rowan-python 2.1.7__py3-none-any.whl → 2.1.9__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/protein.py CHANGED
@@ -114,6 +114,27 @@ class Protein(BaseModel):
114
114
  response = client.post(f"/protein/sanitize/{self.uuid}")
115
115
  response.raise_for_status()
116
116
 
117
+ def download_pdb_file(self, path: Path | None = None, name: str | None = None) -> None:
118
+ """
119
+ Downloads the PDB file for a protein
120
+
121
+ :param path: Directory to save the file to (defaults to current directory)
122
+ :param name: Optional custom name for the file (defaults to protein name)
123
+ :raises requests.HTTPError: if the request to the API fails
124
+ """
125
+ if path is None:
126
+ path = Path.cwd()
127
+
128
+ path.mkdir(parents=True, exist_ok=True)
129
+
130
+ with api_client() as client:
131
+ response = client.get(f"/protein/{self.uuid}/get_pdb_file")
132
+ response.raise_for_status()
133
+
134
+ file_path = path / f"{name or self.name}.pdb"
135
+ with open(file_path, "wb") as f:
136
+ f.write(response.content)
137
+
117
138
 
118
139
  def retrieve_protein(uuid: str) -> Protein:
119
140
  """
@@ -844,7 +844,7 @@ async def _single_conformers(
844
844
  raise NoConformersError("This molecule has no conformers")
845
845
 
846
846
  sorted_data = sorted(
847
- zip(data["energies"], data["conformer_uuids"]),
847
+ zip(data["energies"], data["conformer_uuids"], strict=True),
848
848
  key=lambda x: x[0],
849
849
  )
850
850
 
rowan/workflow.py CHANGED
@@ -1,5 +1,7 @@
1
+ import logging
1
2
  import time
2
3
  from datetime import datetime
4
+ from pathlib import Path
3
5
  from typing import Any, Literal, Self, TypeAlias
4
6
 
5
7
  import stjames
@@ -10,6 +12,9 @@ from stjames.optimization.freezing_string_method import FSMSettings
10
12
  from .protein import Protein
11
13
  from .utils import api_client
12
14
 
15
+ logger = logging.getLogger(__name__)
16
+ logger.setLevel(logging.INFO)
17
+
13
18
  RdkitMol: TypeAlias = Chem.rdchem.Mol | Chem.rdchem.RWMol
14
19
  StJamesMolecule: TypeAlias = stjames.Molecule
15
20
 
@@ -216,6 +221,54 @@ class Workflow(BaseModel):
216
221
  response = client.delete(f"/workflow/{self.uuid}/delete_workflow_data")
217
222
  response.raise_for_status()
218
223
 
224
+ def download_msa_files(self, msa_format: stjames.MSAFormat, path: Path | None = None) -> None:
225
+ if self.workflow_type != "msa":
226
+ raise ValueError("This workflow is not an MSA workflow.")
227
+
228
+ if path is None:
229
+ path = Path.cwd()
230
+
231
+ if not path.exists():
232
+ path.mkdir(parents=True, exist_ok=True)
233
+
234
+ with api_client() as client:
235
+ response = client.get(
236
+ f"/workflow/{self.uuid}/get_msa_files", params={"msa_format": msa_format.value}
237
+ )
238
+ response.raise_for_status()
239
+
240
+ with open(path / f"{self.name}-msa.tar.gz", "wb") as f:
241
+ f.write(response.content)
242
+
243
+ def download_dcd_files(self, replicates: list[int],
244
+ name: str | None = None, path: Path | None = None) -> None:
245
+ """
246
+ Downloads DCD trajectory files for specified replicates
247
+
248
+ :param replicates: List of replicate indices to download
249
+ :param name: Optional custom name for the tar.gz file (defaults to workflow name)
250
+ :param path: Directory to save the file to (defaults to current directory)
251
+ :raises ValueError: if workflow is not a pose analysis MD workflow
252
+ :raises requests.HTTPError: if the request to the API fails
253
+ """
254
+ if self.workflow_type != "pose_analysis_md":
255
+ raise ValueError("This workflow is not a pose analysis molecular dynamics workflow.")
256
+
257
+ if path is None:
258
+ path = Path.cwd()
259
+
260
+ path.mkdir(parents=True, exist_ok=True)
261
+
262
+ with api_client() as client:
263
+ response = client.post(
264
+ f"/trajectory/{self.uuid}/trajectory_dcds", json=replicates
265
+ )
266
+ response.raise_for_status()
267
+
268
+ file_path = path / f"{name or self.name}.tar.gz"
269
+ with open(file_path, "wb") as f:
270
+ f.write(response.content)
271
+
219
272
 
220
273
  def submit_workflow(
221
274
  workflow_type: stjames.WORKFLOW_NAME,
@@ -272,6 +325,75 @@ def submit_workflow(
272
325
  return Workflow(**response.json())
273
326
 
274
327
 
328
+ def batch_submit_workflow(
329
+ workflow_type: stjames.WORKFLOW_NAME,
330
+ workflow_data: dict[str, Any] | None = None,
331
+ initial_molecules: list[dict[str, Any] | StJamesMolecule | RdkitMol] | None = None,
332
+ initial_smileses: list[str] | None = None,
333
+ names: list[str] | None = None,
334
+ folder_uuid: str | None = None,
335
+ max_credits: int | None = None,
336
+ ) -> list[Workflow]:
337
+ """
338
+ Submits a batch of workflows to the API. Each workflow will be submitted with the
339
+ same workflow type, workflow data, and folder UUID, but with different initial molecules and/or
340
+ SMILES strings.
341
+
342
+ :param workflow_type: The type of workflow to submit.
343
+ :param workflow_data: A dictionary containing the data required to run the workflow.
344
+ :param initial_molecules: A list of molecule objects to use as the initial molecules in the
345
+ workflows.
346
+ At least one of a molecule or SMILES must be provided.
347
+ :param initial_smileses: A list of SMILES strings to use as the initial molecules in the
348
+ workflows.
349
+ At least one of a molecule or SMILES must be provided.
350
+ :param names: A list of names to give to the workflows.
351
+ :param folder_uuid: The UUID of the folder to store the workflow in.
352
+ :param max_credits: The maximum number of credits to use for the workflow. This applies per
353
+ workflow, not for the batch.
354
+ :return: A list of Workflow objects representing the submitted workflows.
355
+ :raises ValueError: If neither `initial_smiles` nor a valid `initial_molecule` is provided.
356
+ :raises HTTPError: If the API request fails.
357
+ """
358
+ if workflow_type not in stjames.WORKFLOW_MAPPING:
359
+ raise ValueError(
360
+ "Invalid workflow type. Must be one of:\n " + "\n ".join(stjames.WORKFLOW_MAPPING)
361
+ )
362
+
363
+ if initial_molecules and initial_smileses:
364
+ raise ValueError(
365
+ "You must provide either `initial_molecules` or `initial_smileses`, but not both."
366
+ )
367
+
368
+ if names:
369
+ if initial_molecules and len(names) != len(initial_molecules):
370
+ logger.warning(
371
+ "The number of names is not the same as the number of initial "
372
+ "molecules. Generic names of the form 'Batch Workflow {i}' "
373
+ "will be used."
374
+ )
375
+ if initial_smileses and len(names) != len(initial_smileses):
376
+ logger.warning(
377
+ "The number of names is not the same as the number of initial SMILES."
378
+ "Generic names of the form 'Batch Workflow {i}' will be used."
379
+ )
380
+
381
+ data = {
382
+ "names": names,
383
+ "folder_uuid": folder_uuid,
384
+ "workflow_type": workflow_type,
385
+ "workflow_data": workflow_data,
386
+ "max_credits": max_credits,
387
+ "initial_molecules": initial_molecules,
388
+ "initial_smileses": initial_smileses,
389
+ }
390
+
391
+ with api_client() as client:
392
+ response = client.post("/workflow/batch_submit", json=data)
393
+ response.raise_for_status()
394
+ return [Workflow(**workflow_data) for workflow_data in response.json()]
395
+
396
+
275
397
  def retrieve_workflow(uuid: str) -> Workflow:
276
398
  """
277
399
  Retrieves a workflow from the API.
@@ -402,7 +524,7 @@ def submit_basic_calculation_workflow(
402
524
  tasks=tasks,
403
525
  mode=mode,
404
526
  ),
405
- engine=engine,
527
+ engine=engine or method.default_engine(),
406
528
  )
407
529
 
408
530
  data = {
@@ -551,8 +673,10 @@ def submit_solubility_workflow(
551
673
 
552
674
 
553
675
  def submit_pka_workflow(
554
- initial_molecule: dict[str, Any] | StJamesMolecule | RdkitMol,
676
+ initial_molecule: dict[str, Any] | StJamesMolecule | RdkitMol | str,
555
677
  pka_range: tuple[int, int] = (2, 12),
678
+ method: Literal["aimnet2_wagen2024", "chemprop_nevolianis2025"] = "aimnet2_wagen2024",
679
+ solvent: str = "water",
556
680
  deprotonate_elements: list[int] | None = None,
557
681
  protonate_elements: list[int] | None = None,
558
682
  mode: str = "careful",
@@ -564,7 +688,10 @@ def submit_pka_workflow(
564
688
  Submits a pKa workflow to the API.
565
689
 
566
690
  :param initial_molecule: The molecule to calculate the pKa of.
691
+ Valid input types include `stjames.Molecule`, RDKit molecule, and SMILES.
567
692
  :param pka_range: The range of pKa values to calculate.
693
+ :param method: The algorithm used to compute pKa values.
694
+ :param solvent: The solvent in which pKa values will be computed.
568
695
  :param deprotonate_elements: The elements to deprotonate. Given by atomic number.
569
696
  :param protonate_elements: The elements to protonate. Given by atomic number.
570
697
  :param mode: The mode to run the calculation in. See
@@ -576,20 +703,30 @@ def submit_pka_workflow(
576
703
  :return: A Workflow object representing the submitted workflow.
577
704
  :raises requests.HTTPError: if the request to the API fails.
578
705
  """
579
- if isinstance(initial_molecule, StJamesMolecule):
580
- initial_molecule = initial_molecule.model_dump()
706
+ initial_smiles: str = ""
707
+ initial_stjames_mol: StJamesMolecule | None = None
708
+
709
+ if isinstance(initial_molecule, dict):
710
+ initial_stjames_mol = StJamesMolecule.model_validate(initial_molecule)
711
+ elif isinstance(initial_molecule, StJamesMolecule):
712
+ initial_stjames_mol = initial_molecule
581
713
  elif isinstance(initial_molecule, RdkitMol):
582
- initial_molecule = StJamesMolecule.from_rdkit(initial_molecule, cid=0)
714
+ initial_stjames_mol = StJamesMolecule.from_rdkit(initial_molecule, cid=0)
715
+ elif isinstance(initial_molecule, str):
716
+ initial_smiles = initial_molecule
583
717
 
584
718
  protonate_elements = protonate_elements or [7]
585
719
  deprotonate_elements = deprotonate_elements or [7, 8, 16]
586
720
 
587
721
  workflow = stjames.pKaWorkflow(
588
- initial_molecule=initial_molecule,
722
+ initial_molecule=initial_stjames_mol,
723
+ initial_smiles=initial_smiles,
589
724
  pka_range=pka_range,
590
725
  deprotonate_elements=deprotonate_elements,
591
726
  protonate_elements=protonate_elements,
592
727
  mode=mode,
728
+ solvent=solvent,
729
+ microscopic_pka_method=method,
593
730
  )
594
731
 
595
732
  data = {
@@ -597,7 +734,8 @@ def submit_pka_workflow(
597
734
  "folder_uuid": folder_uuid,
598
735
  "workflow_type": "pka",
599
736
  "workflow_data": workflow.model_dump(),
600
- "initial_molecule": initial_molecule,
737
+ "initial_molecule": initial_stjames_mol.model_dump() if initial_stjames_mol else None,
738
+ "initial_smiles": initial_smiles,
601
739
  "max_credits": max_credits,
602
740
  }
603
741
 
@@ -1040,6 +1178,9 @@ def submit_docking_workflow(
1040
1178
  protein: str | Protein,
1041
1179
  pocket: list[list[float]],
1042
1180
  initial_molecule: dict[str, Any] | StJamesMolecule | RdkitMol | None = None,
1181
+ executable: str = "vina",
1182
+ scoring_function: str = "vinardo",
1183
+ exhaustiveness: float = 8,
1043
1184
  do_csearch: bool = False,
1044
1185
  do_optimization: bool = False,
1045
1186
  do_pose_refinement: bool = False,
@@ -1050,15 +1191,18 @@ def submit_docking_workflow(
1050
1191
  """
1051
1192
  Submits a Docking workflow to the API.
1052
1193
 
1053
- :param protein: The protein to dock. Can be fed as a uuid or a Protein object.
1194
+ :param protein: The protein to dock. Can be input as a uuid or a Protein object.
1054
1195
  :param initial_molecule: The initial molecule to be docked
1196
+ :param executable: Which docking implementation to use.
1197
+ :param scoring_function: Which docking scoring function to use.
1198
+ :param exhaustiveness: Which exhaustiveness to employ.
1055
1199
  :param do_csearch: Whether to perform a conformational search on the ligand.
1056
1200
  :param do_optimization: Whether to perform an optimization on the ligand.
1057
1201
  :param do_pose_refinement: Whether or not to optimize output poses.
1058
1202
  :param name: The name of the workflow.
1059
1203
  :param folder_uuid: The UUID of the folder to place the workflow in.
1060
1204
  :param max_credits: The maximum number of credits to use for the workflow.
1061
- :return: A Workflow object representing the submitted IRC workflow.
1205
+ :return: A Workflow object representing the submitted docking workflow.
1062
1206
  :raises requests.HTTPError: if the request to the API fails.
1063
1207
  """
1064
1208
 
@@ -1070,6 +1214,12 @@ def submit_docking_workflow(
1070
1214
  if isinstance(protein, Protein):
1071
1215
  protein = protein.uuid
1072
1216
 
1217
+ docking_settings = {
1218
+ "executable": executable,
1219
+ "exhaustiveness": exhaustiveness,
1220
+ "scoring_function": scoring_function,
1221
+ }
1222
+
1073
1223
  workflow = stjames.DockingWorkflow(
1074
1224
  initial_molecule=initial_molecule,
1075
1225
  target_uuid=protein,
@@ -1077,6 +1227,7 @@ def submit_docking_workflow(
1077
1227
  do_csearch=do_csearch,
1078
1228
  do_optimization=do_optimization,
1079
1229
  do_pose_refinement=do_pose_refinement,
1230
+ docking_settings=docking_settings,
1080
1231
  )
1081
1232
 
1082
1233
  data = {
@@ -1283,3 +1434,146 @@ def submit_double_ended_ts_search_workflow(
1283
1434
  response = client.post("/workflow", json=data)
1284
1435
  response.raise_for_status()
1285
1436
  return Workflow(**response.json())
1437
+
1438
+
1439
+ def submit_pose_analysis_md_workflow(
1440
+ protein: str | Protein,
1441
+ initial_smiles: str,
1442
+ num_trajectories: int = 1,
1443
+ simulation_time_ns: int = 10,
1444
+ ligand_residue_name: str = "LIG",
1445
+ name: str = "Pose-Analysis MD Workflow",
1446
+ folder_uuid: str | None = None,
1447
+ max_credits: int | None = None,
1448
+ ) -> Workflow:
1449
+ """
1450
+ Submits a pose-analysis MD workflow to the API.
1451
+
1452
+ :param protein: The *holo* protein on which MD will be run.
1453
+ Can be input as a uuid or a Protein object.
1454
+ :param initial_smiles: The SMILES for the ligand.
1455
+ :param num_trajectories: The number of trajectories to run.
1456
+ :param simulation_time_ns: How long to run the simulation for, in nanoseconds.
1457
+ :param ligand_residue_name: The name of the residue corresponding to the ligand.
1458
+ :param name: The name of the workflow.
1459
+ :param folder_uuid: The UUID of the folder to place the workflow in.
1460
+ :param max_credits: The maximum number of credits to use for the workflow.
1461
+ :return: A Workflow object representing the submitted workflow.
1462
+ :raises requests.HTTPError: if the request to the API fails.
1463
+ """
1464
+
1465
+ if isinstance(protein, Protein):
1466
+ protein = protein.uuid
1467
+
1468
+ workflow = stjames.PoseAnalysisMolecularDynamicsWorkflow(
1469
+ protein_uuid=protein,
1470
+ initial_smiles=initial_smiles,
1471
+ num_trajectories=num_trajectories,
1472
+ simulation_time_ns=simulation_time_ns,
1473
+ ligand_residue_name=ligand_residue_name,
1474
+ )
1475
+
1476
+ data = {
1477
+ "name": name,
1478
+ "folder_uuid": folder_uuid,
1479
+ "workflow_type": "pose_analysis_md",
1480
+ "workflow_data": workflow.model_dump(serialize_as_any=True),
1481
+ "initial_smiles": initial_smiles,
1482
+ "max_credits": max_credits,
1483
+ }
1484
+
1485
+ with api_client() as client:
1486
+ response = client.post("/workflow", json=data)
1487
+ response.raise_for_status()
1488
+ return Workflow(**response.json())
1489
+
1490
+
1491
+ def submit_batch_docking_workflow(
1492
+ smiles_list: list[str],
1493
+ protein: str | Protein,
1494
+ pocket: list[list[float]],
1495
+ executable: str = "qvina2",
1496
+ scoring_function: str = "vina",
1497
+ exhaustiveness: float = 8,
1498
+ name: str = "Batch Docking Workflow",
1499
+ folder_uuid: str | None = None,
1500
+ max_credits: int | None = None,
1501
+ ) -> Workflow:
1502
+ """
1503
+ Submits a batch docking workflow to the API.
1504
+
1505
+ :param smiles_list: The SMILES strings to dock.
1506
+ :param protein: The protein to dock. Can be input as a uuid or a Protein object.
1507
+ :param executable: Which docking implementation to use.
1508
+ :param scoring_function: Which docking scoring function to use.
1509
+ :param exhaustiveness: Which exhaustiveness to employ.
1510
+ :param name: The name of the workflow.
1511
+ :param folder_uuid: The UUID of the folder to place the workflow in.
1512
+ :param max_credits: The maximum number of credits to use for the workflow.
1513
+ :return: A Workflow object representing the submitted batch docking workflow.
1514
+ :raises requests.HTTPError: if the request to the API fails.
1515
+ """
1516
+
1517
+ docking_settings = {
1518
+ "executable": executable,
1519
+ "exhaustiveness": exhaustiveness,
1520
+ "scoring_function": scoring_function,
1521
+ }
1522
+
1523
+ workflow = stjames.BatchDockingWorkflow(
1524
+ initial_smiles_list=smiles_list,
1525
+ target=protein,
1526
+ pocket=pocket,
1527
+ docking_settings=docking_settings,
1528
+ )
1529
+
1530
+ data = {
1531
+ "name": name,
1532
+ "folder_uuid": folder_uuid,
1533
+ "workflow_type": "batch_docking",
1534
+ "workflow_data": workflow.model_dump(serialize_as_any=True),
1535
+ "max_credits": max_credits,
1536
+ }
1537
+
1538
+ with api_client() as client:
1539
+ response = client.post("/workflow", json=data)
1540
+ response.raise_for_status()
1541
+ return Workflow(**response.json())
1542
+
1543
+
1544
+ def submit_msa_workflow(
1545
+ initial_protein_sequences: list[stjames.ProteinSequence | str],
1546
+ output_formats: list[stjames.MSAFormat],
1547
+ name: str = "MSA Workflow",
1548
+ folder_uuid: str | None = None,
1549
+ max_credits: int | None = None,
1550
+ ) -> Workflow:
1551
+ """
1552
+ Submits a multiple sequence alignment (MSA) workflow to the API.
1553
+
1554
+ :param initial_protein_sequences: List of protein sequences to align, as ProteinSequence objects
1555
+ sor strings.
1556
+ :param output_formats: List of output formats for the resulting MSA files.
1557
+ :param name: The name to assign to the workflow.
1558
+ :param folder_uuid: UUID of the folder where the workflow will be stored.
1559
+ :param max_credits: The maximum number of credits to use for the workflow.
1560
+ :return: A Workflow object representing the submitted MSA workflow.
1561
+ :raises HTTPError: If the API request fails.
1562
+ """
1563
+ workflow = stjames.MSAWorkflow(
1564
+ initial_protein_sequences=initial_protein_sequences,
1565
+ output_formats=output_formats,
1566
+ )
1567
+
1568
+ data = {
1569
+ "name": name,
1570
+ "folder_uuid": folder_uuid,
1571
+ "workflow_type": "msa",
1572
+ "workflow_data": workflow.model_dump(serialize_as_any=True),
1573
+ "max_credits": max_credits,
1574
+ }
1575
+
1576
+ with api_client() as client:
1577
+ response = client.post("/workflow", json=data)
1578
+ response.raise_for_status()
1579
+ return Workflow(**response.json())
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rowan-python
3
- Version: 2.1.7
3
+ Version: 2.1.9
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
7
7
  Author-email: Corin Wagen <corin@rowansci.com>
8
8
  License-File: LICENSE
9
- Requires-Python: >=3.9
9
+ Requires-Python: >=3.11
10
10
  Requires-Dist: httpx
11
11
  Requires-Dist: nest-asyncio
12
12
  Requires-Dist: rdkit
13
13
  Requires-Dist: setuptools
14
- Requires-Dist: stjames>=0.0.109
14
+ Requires-Dist: stjames>=0.0.125
15
15
  Description-Content-Type: text/markdown
16
16
 
17
17
  # Rowan Python Library
@@ -2,14 +2,14 @@ rowan/__init__.py,sha256=2rz6dW0l9DzawiFi6S0WSv8XlZq1CoTBKJrsJ1uesvk,171
2
2
  rowan/constants.py,sha256=emCH4m9OL2Hm5E-6mJGM_FgzrK_JrZT-FiKJ6pMNQ4Y,84
3
3
  rowan/folder.py,sha256=n9WkjHMweQLtVcWUvCttOrmezvUdbFxam_eDEMzLF_A,6791
4
4
  rowan/project.py,sha256=ALPPkMa_cg7w5OkXno1cs6acCofw8AOUYRSeWgr3L0o,3774
5
- rowan/protein.py,sha256=bMemvLZua_pnTrYOxHFZ4jFlRH9KgpYvtjj5M2__28k,8026
5
+ rowan/protein.py,sha256=mFSVCr-08bSikXBUtJtSWzjKcVAuBmTeRckn7JUHYSE,8810
6
6
  rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  rowan/user.py,sha256=Dl--NPUPATKCs2VmILsW8HnLiunG0Lxr0n6mKuHm21U,3891
8
8
  rowan/utils.py,sha256=64II-cPOe_SFJK302Bm8hP62d_3_CgnTVYCbn3zKT7U,3334
9
- rowan/workflow.py,sha256=ec4ygHqsyldHn6-gpyPwzHfsGcQXRZOauIjdHtdzJuE,48826
9
+ rowan/workflow.py,sha256=ovhjVUaoI0K-pepk3u_xGnymBOq9cEEUEoyzsFr36Sc,60331
10
10
  rowan/rowan_rdkit/__init__.py,sha256=EATX2VRzywzKxqkpCUMTf7RNQLkWsfi5VcCNDW6EIiw,503
11
- rowan/rowan_rdkit/chem_utils.py,sha256=i7-EmAcmvHYtc9NiZblLY_k2DoQKofAZo5KT2qtkUCI,34775
12
- rowan_python-2.1.7.dist-info/METADATA,sha256=XWltMwFRX9VyDOTy7rUE86IDXL8xBakq-SCcJpDBOPo,1599
13
- rowan_python-2.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- rowan_python-2.1.7.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
15
- rowan_python-2.1.7.dist-info/RECORD,,
11
+ rowan/rowan_rdkit/chem_utils.py,sha256=sKCzul2e0ldVYTBImhTwso7ddNgPKmvS-OmvCEjVJH0,34788
12
+ rowan_python-2.1.9.dist-info/METADATA,sha256=Teu23XCt-9sjkXXVcsjC5PID3Pgp5bR-conmLvaPiFE,1600
13
+ rowan_python-2.1.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ rowan_python-2.1.9.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
15
+ rowan_python-2.1.9.dist-info/RECORD,,