rowan-mcp 1.0.2__py3-none-any.whl → 2.0.0__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.

Potentially problematic release.


This version of rowan-mcp might be problematic. Click here for more details.

Files changed (70) hide show
  1. rowan_mcp/__init__.py +1 -1
  2. rowan_mcp/__main__.py +3 -5
  3. rowan_mcp/functions_v2/BENCHMARK.md +86 -0
  4. rowan_mcp/functions_v2/molecule_lookup.py +232 -0
  5. rowan_mcp/functions_v2/protein_management.py +141 -0
  6. rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
  7. rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
  8. rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
  9. rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
  10. rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
  11. rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
  12. rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
  13. rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
  14. rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
  15. rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
  16. rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
  17. rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
  18. rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
  19. rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
  20. rowan_mcp/server.py +109 -144
  21. rowan_mcp/tests/basic_calculation_from_json.py +0 -0
  22. rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
  23. rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
  24. rowan_mcp/tests/bde.py +37 -0
  25. rowan_mcp/tests/benchmark_queries.md +120 -0
  26. rowan_mcp/tests/cofolding_screen.py +131 -0
  27. rowan_mcp/tests/conformer_dependent_redox.py +37 -0
  28. rowan_mcp/tests/conformers.py +31 -0
  29. rowan_mcp/tests/data.json +189 -0
  30. rowan_mcp/tests/docking_screen.py +157 -0
  31. rowan_mcp/tests/irc.py +24 -0
  32. rowan_mcp/tests/macropka.py +13 -0
  33. rowan_mcp/tests/multistage_opt.py +13 -0
  34. rowan_mcp/tests/optimization.py +21 -0
  35. rowan_mcp/tests/phenol_pka.py +36 -0
  36. rowan_mcp/tests/pka.py +36 -0
  37. rowan_mcp/tests/protein_cofolding.py +17 -0
  38. rowan_mcp/tests/scan.py +28 -0
  39. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/METADATA +41 -33
  40. rowan_mcp-2.0.0.dist-info/RECORD +42 -0
  41. rowan_mcp/functions/admet.py +0 -94
  42. rowan_mcp/functions/bde.py +0 -113
  43. rowan_mcp/functions/calculation_retrieve.py +0 -89
  44. rowan_mcp/functions/conformers.py +0 -80
  45. rowan_mcp/functions/descriptors.py +0 -92
  46. rowan_mcp/functions/docking.py +0 -340
  47. rowan_mcp/functions/docking_enhanced.py +0 -174
  48. rowan_mcp/functions/electronic_properties.py +0 -205
  49. rowan_mcp/functions/folder_management.py +0 -137
  50. rowan_mcp/functions/fukui.py +0 -219
  51. rowan_mcp/functions/hydrogen_bond_basicity.py +0 -94
  52. rowan_mcp/functions/irc.py +0 -125
  53. rowan_mcp/functions/macropka.py +0 -120
  54. rowan_mcp/functions/molecular_converter.py +0 -423
  55. rowan_mcp/functions/molecular_dynamics.py +0 -191
  56. rowan_mcp/functions/molecule_lookup.py +0 -57
  57. rowan_mcp/functions/multistage_opt.py +0 -171
  58. rowan_mcp/functions/pdb_handler.py +0 -200
  59. rowan_mcp/functions/pka.py +0 -88
  60. rowan_mcp/functions/redox_potential.py +0 -352
  61. rowan_mcp/functions/scan.py +0 -536
  62. rowan_mcp/functions/scan_analyzer.py +0 -347
  63. rowan_mcp/functions/solubility.py +0 -277
  64. rowan_mcp/functions/spin_states.py +0 -747
  65. rowan_mcp/functions/system_management.py +0 -368
  66. rowan_mcp/functions/tautomers.py +0 -91
  67. rowan_mcp/functions/workflow_management.py +0 -422
  68. rowan_mcp-1.0.2.dist-info/RECORD +0 -34
  69. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/WHEEL +0 -0
  70. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,368 +0,0 @@
1
- """
2
- System management operations for Rowan API.
3
- """
4
-
5
- import os
6
- import sys
7
- import rowan
8
- import logging
9
- from typing import Optional
10
-
11
- # Set up logging
12
- logger = logging.getLogger(__name__)
13
-
14
- # Configure rowan API key
15
- if not hasattr(rowan, 'api_key') or not rowan.api_key:
16
- api_key = os.getenv("ROWAN_API_KEY")
17
- if api_key:
18
- rowan.api_key = api_key
19
- logger.info("Rowan API key configured")
20
- else:
21
- logger.error("No ROWAN_API_KEY found in environment")
22
-
23
- def rowan_system_management(
24
- action: str,
25
- job_uuid: Optional[str] = None,
26
- log_level: Optional[str] = None
27
- ) -> str:
28
- """Unified system management tool for server utilities and information.
29
-
30
- **Available Actions:**
31
- - **help**: Get list of all available Rowan MCP tools with descriptions
32
- - **server_info**: Get server status and configuration information
33
- - **server_status**: Check server health and connectivity
34
- - **set_log_level**: Set logging level for debugging (requires: log_level)
35
- - **job_redirect**: Redirect legacy job queries to workflow management (requires: job_uuid)
36
-
37
- Args:
38
- action: Action to perform ('help', 'server_info', 'server_status', 'set_log_level', 'job_redirect')
39
- job_uuid: UUID of the job (required for job_redirect)
40
- log_level: Logging level - DEBUG, INFO, WARNING, ERROR (required for set_log_level)
41
-
42
- Returns:
43
- Results of the system operation
44
- """
45
-
46
- action = action.lower()
47
-
48
- try:
49
- if action == "help":
50
- result = "**Available Rowan MCP Tools** \n\n"
51
-
52
- result += "**Now with unified management tools!**\n"
53
- result += "Each tool has tailored documentation and parameters.\n\n"
54
-
55
- # Group by common use cases
56
- result += "**Quantum Chemistry & Basic Calculations:**\n"
57
- result += "• `rowan_electronic_properties` - HOMO/LUMO, orbitals\n"
58
- result += "• `rowan_multistage_opt` - Multi-level optimization (for geometry)\n\n"
59
-
60
- result += "**Molecular Analysis:**\n"
61
- result += "• `rowan_conformers` - Find molecular conformations\n"
62
- result += "• `rowan_tautomers` - Tautomer enumeration\n"
63
- result += "• `rowan_descriptors` - Molecular descriptors for ML\n\n"
64
-
65
- result += "**Chemical Properties:**\n"
66
- result += "• `rowan_pka` - pKa prediction\n"
67
- result += "• `rowan_redox_potential` - Redox potentials vs SCE\n"
68
-
69
- result += "• `rowan_solubility` - Solubility prediction\n\n"
70
-
71
- result += "**Drug Discovery:**\n"
72
- result += "• `rowan_admet` - ADME-Tox properties\n"
73
- result += "• `rowan_docking` - Protein-ligand docking\n\n"
74
-
75
- result += "**Advanced Analysis:**\n"
76
- result += "• `rowan_scan` - Potential energy surface scans (bond/angle/dihedral)\n"
77
- result += "• `rowan_fukui` - Reactivity analysis\n"
78
- result += "• `rowan_spin_states` - Spin state preferences\n"
79
- result += "• `rowan_irc` - Reaction coordinate following\n"
80
- result += "• `rowan_molecular_dynamics` - MD simulations\n"
81
- result += "\n"
82
-
83
- result += "**Usage Guidelines:**\n"
84
- result += "• For geometry optimization: use `rowan_multistage_opt`\n"
85
- result += "• For conformer search: use `rowan_conformers`\n"
86
- result += "• For pKa prediction: use `rowan_pka`\n"
87
- result += "• For electronic structure: use `rowan_electronic_properties`\n"
88
- result += "• For drug properties: use `rowan_admet`\n"
89
- result += "• For reaction mechanisms: use `rowan_scan` then `rowan_irc`\n"
90
- result += "• For potential energy scans: use `rowan_scan` with coordinate specification\n\n"
91
-
92
- result += "**Management Tools:**\n"
93
- result += "• `rowan_folder_management` - Unified folder operations (create, retrieve, update, delete, list)\n"
94
- result += "• `rowan_workflow_management` - Unified workflow operations (create, retrieve, update, stop, status, delete, list)\n"
95
- result += "• `rowan_system_management` - System utilities (help, set_log_level, job_redirect)\n"
96
- result += "• `rowan_calculation_retrieve` - Get calculation results\n"
97
- result += "• `rowan_molecule_lookup` - SMILES lookup for common molecules\n\n"
98
-
99
- result += "**Total Available:** 20+ specialized tools + management tools\n"
100
- result += "**Each tool has specific documentation - check individual tool descriptions**\n"
101
- result += "**Management tools use 'action' parameter to specify operation**\n"
102
-
103
- return result
104
-
105
- elif action == "server_info":
106
- result = "**Rowan MCP Server Information**\n\n"
107
-
108
- # Server configuration
109
- result += "**Configuration:**\n"
110
- result += f"• API Key: {'Configured' if os.getenv('ROWAN_API_KEY') else 'Missing'}\n"
111
- result += f"• Rowan Package: {'Available' if rowan else 'Not Found'}\n"
112
- result += f"• Log Level: {logger.level}\n"
113
- result += f"• Python Version: {sys.version.split()[0]}\n\n"
114
-
115
- # Available tools count
116
- result += f"**Available Tools:** 20+ specialized computational chemistry tools\n"
117
- result += f"• Core Calculations: 4 tools (electronic_properties, multistage_opt, etc.)\n"
118
- result += f"• Molecular Analysis: 3 tools (conformers, tautomers, descriptors)\n"
119
- result += f"• Chemical Properties: 4 tools (pka, redox_potential, bde, solubility)\n"
120
- result += f"• Advanced Analysis: 6 tools (scan, fukui, spin_states, irc, md, etc.)\n"
121
- result += f"• Drug Discovery: 2 tools (admet, docking)\n"
122
- result += f"• Management Tools: 5 tools (workflow, folder, system, etc.)\n\n"
123
-
124
- # Quick status check
125
- try:
126
- # Try a simple API call to test connectivity
127
- recent_workflows = rowan.Workflow.list(size=1)
128
- total_workflows = len(recent_workflows.get('workflows', []))
129
- result += f"**API Connectivity:** Connected\n"
130
- result += f"**Workflow Check:** {total_workflows} workflows accessible\n\n"
131
- except Exception as e:
132
- result += f"**API Connectivity:** Error: {str(e)[:100]}...\n\n"
133
-
134
- result += f"**Usage:**\n"
135
- result += f"• Use rowan_system_management(action='help') for tool descriptions\n"
136
- result += f"• Use rowan_workflow_management(action='list') to see your workflows\n"
137
- result += f"• Note: Folder management currently has API issues - use workflow organization instead\n"
138
-
139
- return result
140
-
141
- elif action == "server_status":
142
- result = "**Rowan MCP Server Health Check**\n\n"
143
-
144
- # Test API connectivity with workflow endpoint (the main one)
145
- status_checks = []
146
-
147
- # Test workflow list
148
- try:
149
- workflows = rowan.Workflow.list(size=1)
150
- workflow_count = len(workflows.get('workflows', []))
151
- status_checks.append(("Workflow API", "OK", f"{workflow_count} workflows accessible"))
152
- except Exception as e:
153
- status_checks.append(("Workflow API", "Error", str(e)[:50]))
154
-
155
- # Test workflow retrieve (if we have any workflows)
156
- try:
157
- workflows = rowan.Workflow.list(size=1)
158
- if workflows.get('workflows'):
159
- first_workflow_uuid = workflows['workflows'][0]['uuid']
160
- rowan.Workflow.retrieve(uuid=first_workflow_uuid)
161
- status_checks.append(("Workflow Retrieve", "OK", "Can access workflow details"))
162
- else:
163
- status_checks.append(("Workflow Retrieve", "Skipped", "No workflows to test"))
164
- except Exception as e:
165
- status_checks.append(("Workflow Retrieve", "Error", str(e)[:50]))
166
-
167
- # Display results
168
- for check_name, status, details in status_checks:
169
- result += f"**{check_name}:** {status}\n"
170
- result += f" Details: {details}\n\n"
171
-
172
- # Overall status
173
- all_ok = all("OK" in check[1] or "Skipped" in check[1] for check in status_checks)
174
- result += f"**Overall Status:** {'All Systems Operational' if all_ok else 'Some Issues Detected'}\n\n"
175
-
176
- if not all_ok:
177
- result += f"**Troubleshooting:**\n"
178
- result += f"• Check your ROWAN_API_KEY environment variable\n"
179
- result += f"• Verify internet connectivity\n"
180
- result += f"• Check Rowan service status at labs.rowansci.com\n"
181
-
182
- return result
183
-
184
- elif action == "set_log_level":
185
- if not log_level:
186
- return "Error: 'log_level' is required for set_log_level action"
187
-
188
- valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
189
- log_level = log_level.upper()
190
-
191
- if log_level not in valid_levels:
192
- return f"Invalid log level. Use one of: {', '.join(valid_levels)}"
193
-
194
- logger.setLevel(getattr(logging, log_level))
195
- logger.info(f"Log level changed to: {log_level}")
196
-
197
- return f"Log level set to {log_level}"
198
-
199
- elif action == "job_redirect":
200
- if not job_uuid:
201
- return "Error: 'job_uuid' is required for job_redirect action"
202
-
203
- # Try to treat the job_uuid as a workflow_uuid and retrieve results directly
204
- try:
205
- workflow = rowan.Workflow.retrieve(uuid=job_uuid)
206
-
207
- # Get status and interpret it
208
- status = workflow.get('object_status', 'Unknown')
209
- status_names = {
210
- 0: "Queued",
211
- 1: "Running",
212
- 2: "Completed",
213
- 3: "Failed",
214
- 4: "Stopped",
215
- 5: "Awaiting Queue"
216
- }
217
- status_name = status_names.get(status, f"Unknown ({status})")
218
-
219
- formatted = f"**Found Workflow {job_uuid}:**\n\n"
220
- formatted += f"Name: {workflow.get('name', 'N/A')}\n"
221
- formatted += f"Type: {workflow.get('object_type', 'N/A')}\n"
222
- formatted += f"Status: {status_name} ({status})\n"
223
- formatted += f"Created: {workflow.get('created_at', 'N/A')}\n"
224
- formatted += f"Elapsed: {workflow.get('elapsed', 0):.2f}s\n\n"
225
-
226
- if status == 2: # Completed
227
- formatted += f"**Getting Results...**\n\n"
228
-
229
- # Try to retrieve calculation results
230
- try:
231
- calc_result = rowan.Calculation.retrieve(uuid=job_uuid)
232
-
233
- # Extract workflow type to provide specific result formatting
234
- workflow_type = workflow.get('object_type', '')
235
-
236
- if workflow_type == 'electronic_properties':
237
- formatted += f"**Electronic Properties Results:**\n\n"
238
-
239
- # Extract key electronic properties from the result
240
- object_data = calc_result.get('object_data', {})
241
-
242
- # Molecular orbital energies (HOMO/LUMO)
243
- if 'molecular_orbitals' in object_data:
244
- mo_data = object_data['molecular_orbitals']
245
- if isinstance(mo_data, dict) and 'energies' in mo_data:
246
- energies = mo_data['energies']
247
- if isinstance(energies, list) and len(energies) > 0:
248
- # Find HOMO/LUMO
249
- occupations = mo_data.get('occupations', [])
250
- if occupations:
251
- homo_idx = None
252
- lumo_idx = None
253
- for i, occ in enumerate(occupations):
254
- if occ > 0.5: # Occupied
255
- homo_idx = i
256
- elif occ < 0.5 and lumo_idx is None: # First unoccupied
257
- lumo_idx = i
258
- break
259
-
260
- if homo_idx is not None and lumo_idx is not None:
261
- homo_energy = energies[homo_idx]
262
- lumo_energy = energies[lumo_idx]
263
- gap = lumo_energy - homo_energy
264
-
265
- formatted += f"• HOMO Energy: {homo_energy:.4f} hartree ({homo_energy * 27.2114:.2f} eV)\n"
266
- formatted += f"• LUMO Energy: {lumo_energy:.4f} hartree ({lumo_energy * 27.2114:.2f} eV)\n"
267
- formatted += f"• HOMO-LUMO Gap: {gap:.4f} hartree ({gap * 27.2114:.2f} eV)\n\n"
268
-
269
- # Dipole moment
270
- if 'dipole' in object_data:
271
- dipole = object_data['dipole']
272
- if isinstance(dipole, dict) and 'magnitude' in dipole:
273
- formatted += f"**Dipole Moment:** {dipole['magnitude']:.4f} Debye\n\n"
274
- elif isinstance(dipole, (int, float)):
275
- formatted += f"**Dipole Moment:** {dipole:.4f} Debye\n\n"
276
-
277
- # If no specific electronic properties found, show available keys
278
- if not any(key in object_data for key in ['molecular_orbitals', 'dipole']):
279
- if object_data:
280
- formatted += f"**Available Properties:** {', '.join(object_data.keys())}\n\n"
281
- else:
282
- formatted += f"**No electronic properties data found in results**\n\n"
283
-
284
- else:
285
- # For other workflow types, show general calculation results
286
- formatted += f"**{workflow_type.replace('_', ' ').title()} Results:**\n\n"
287
-
288
- object_data = calc_result.get('object_data', {})
289
- if object_data:
290
- # Show first few key-value pairs
291
- count = 0
292
- for key, value in object_data.items():
293
- if count >= 5: # Limit to first 5 items for job_redirect
294
- formatted += f" ... and {len(object_data) - 5} more properties\n"
295
- break
296
-
297
- # Format the value nicely
298
- if isinstance(value, (int, float)):
299
- formatted += f"• **{key}**: {value}\n"
300
- elif isinstance(value, str):
301
- formatted += f"• **{key}**: {value[:50]}{'...' if len(value) > 50 else ''}\n"
302
- elif isinstance(value, list):
303
- formatted += f"• **{key}**: {len(value)} items\n"
304
- elif isinstance(value, dict):
305
- formatted += f"• **{key}**: {len(value)} properties\n"
306
- else:
307
- formatted += f"• **{key}**: {type(value).__name__}\n"
308
- count += 1
309
- formatted += "\n"
310
- else:
311
- formatted += f"**No calculation data found in results**\n\n"
312
-
313
- except Exception as retrieve_error:
314
- formatted += f"**Results retrieval failed:** {str(retrieve_error)}\n\n"
315
-
316
- elif status in [0, 1, 5]: # Still running
317
- formatted += f"**Workflow is still {status_name.lower()}**\n"
318
- formatted += f"Check back later for results\n\n"
319
-
320
- elif status == 3: # Failed
321
- formatted += f"**Workflow failed**\n"
322
- formatted += f"Check the workflow parameters and try again\n\n"
323
-
324
- formatted += f"**For more details:**\n"
325
- formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{job_uuid}') for full workflow details\n"
326
- formatted += f"• Use rowan_calculation_retrieve('{job_uuid}') for raw calculation data\n"
327
-
328
- return formatted
329
-
330
- except Exception as e:
331
- # If workflow retrieval fails, provide the legacy guidance
332
- formatted = f"**Could not find workflow {job_uuid}:** {str(e)}\n\n"
333
- formatted += f"**Important Note:**\n"
334
- formatted += f"Rowan manages computations through workflows, not individual jobs.\n"
335
- formatted += f"The job/results concept is legacy from older versions.\n\n"
336
- formatted += f"**To find your workflow:**\n"
337
- formatted += f"• Use rowan_workflow_management(action='list') to see all workflows\n"
338
- formatted += f"• Look for workflows with similar names or recent creation times\n"
339
- formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='UUID') to get results\n\n"
340
- formatted += f"**Migration Guide:**\n"
341
- formatted += f"• Old: rowan_job_status('{job_uuid}') → New: rowan_workflow_management(action='status', workflow_uuid='UUID')\n"
342
- formatted += f"• Old: rowan_job_results('{job_uuid}') → New: rowan_workflow_management(action='retrieve', workflow_uuid='UUID')\n"
343
-
344
- return formatted
345
-
346
- else:
347
- return f"Error: Unknown action '{action}'. Available actions: help, server_info, server_status, set_log_level, job_redirect"
348
-
349
- except Exception as e:
350
- return f"Error in system {action}: {str(e)}"
351
-
352
-
353
- def test_rowan_system_management():
354
- """Test the rowan_system_management function."""
355
- try:
356
- # Test the help action
357
- result = rowan_system_management(action="help")
358
- print("System management test successful!")
359
- print(f"Result length: {len(result)} characters")
360
- print(f"First line: {result.split(chr(10))[0]}")
361
- return True
362
- except Exception as e:
363
- print(f"System management test failed: {e}")
364
- return False
365
-
366
-
367
- if __name__ == "__main__":
368
- test_rowan_system_management()
@@ -1,91 +0,0 @@
1
- """
2
- Rowan tautomers function for drug design.
3
- """
4
-
5
- import os
6
- import logging
7
- import time
8
- from typing import Optional
9
-
10
- try:
11
- import rowan
12
- except ImportError:
13
- rowan = None
14
-
15
- # Setup logging
16
- logger = logging.getLogger(__name__)
17
-
18
- # Setup API key
19
- api_key = os.getenv("ROWAN_API_KEY")
20
- if rowan and api_key:
21
- rowan.api_key = api_key
22
-
23
- def log_rowan_api_call(workflow_type: str, **kwargs):
24
- """Log Rowan API calls with detailed parameters."""
25
-
26
- try:
27
- start_time = time.time()
28
- result = rowan.compute(workflow_type=workflow_type, **kwargs)
29
- api_time = time.time() - start_time
30
-
31
- if isinstance(result, dict) and 'uuid' in result:
32
- job_status = result.get('status', result.get('object_status', 'Unknown'))
33
- status_names = {0: "Queued", 1: "Running", 2: "Completed", 3: "Failed", 4: "Stopped", 5: "Awaiting Queue"}
34
- status_text = status_names.get(job_status, f"Unknown ({job_status})")
35
-
36
- return result
37
-
38
- except Exception as e:
39
- api_time = time.time() - start_time
40
- raise e
41
-
42
- def rowan_tautomers(
43
- name: str,
44
- molecule: str,
45
- folder_uuid: Optional[str] = None,
46
- blocking: bool = True,
47
- ping_interval: int = 5
48
- ) -> str:
49
- """Enumerate and rank tautomers by stability.
50
-
51
- Finds all possible tautomeric forms and ranks them by relative energy:
52
- - Prototropic tautomers (keto-enol, etc.)
53
- - Relative populations at room temperature
54
- - Dominant tautomeric forms
55
-
56
- Use this for: Drug design, understanding protonation states, reaction mechanisms
57
-
58
- Args:
59
- name: Name for the calculation
60
- molecule: Molecule SMILES string
61
- folder_uuid: Optional folder UUID for organization
62
- blocking: Whether to wait for completion (default: True)
63
- ping_interval: Check status interval in seconds (default: 5)
64
-
65
- Returns:
66
- Tautomer enumeration and ranking results
67
- """
68
- result = log_rowan_api_call(
69
- workflow_type="tautomers",
70
- name=name,
71
- molecule=molecule,
72
- folder_uuid=folder_uuid,
73
- blocking=blocking,
74
- ping_interval=ping_interval
75
- )
76
- return str(result)
77
-
78
- def test_rowan_tautomers():
79
- """Test the rowan_tautomers function."""
80
- try:
81
- # Test with a simple molecule (non-blocking to avoid long wait)
82
- result = rowan_tautomers("test_tautomers", "CCO", blocking=False)
83
- print(" Tautomers test successful!")
84
- print(f"Result length: {len(result)} characters")
85
- return True
86
- except Exception as e:
87
- print(f" Tautomers test failed: {e}")
88
- return False
89
-
90
- if __name__ == "__main__":
91
- test_rowan_tautomers()