mint-sdk 1.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.
Files changed (204) hide show
  1. mint_sdk/__init__.py +354 -0
  2. mint_sdk/_discover.py +313 -0
  3. mint_sdk/_prompt.py +228 -0
  4. mint_sdk/_version.py +24 -0
  5. mint_sdk/add_backend_artifact.py +52 -0
  6. mint_sdk/add_backend_commands.py +356 -0
  7. mint_sdk/add_backend_endpoint.py +38 -0
  8. mint_sdk/add_backend_hook.py +56 -0
  9. mint_sdk/add_backend_job.py +145 -0
  10. mint_sdk/add_backend_migration.py +47 -0
  11. mint_sdk/add_backend_router.py +36 -0
  12. mint_sdk/add_backend_schema.py +42 -0
  13. mint_sdk/add_backend_service.py +56 -0
  14. mint_sdk/add_backend_setting.py +34 -0
  15. mint_sdk/add_backend_wiring.py +97 -0
  16. mint_sdk/add_command.py +67 -0
  17. mint_sdk/add_data_template_backend_templates.py +379 -0
  18. mint_sdk/add_data_template_commands.py +260 -0
  19. mint_sdk/add_data_template_frontend_assay_templates.py +364 -0
  20. mint_sdk/add_data_template_frontend_core_templates.py +194 -0
  21. mint_sdk/add_data_template_frontend_helpers.py +107 -0
  22. mint_sdk/add_data_template_frontend_lab_templates.py +201 -0
  23. mint_sdk/add_data_template_frontend_pack_templates.py +249 -0
  24. mint_sdk/add_data_template_frontend_panel_templates.py +199 -0
  25. mint_sdk/add_data_template_frontend_templates.py +145 -0
  26. mint_sdk/add_data_template_frontend_workflow_templates.py +206 -0
  27. mint_sdk/add_data_template_router_templates.py +295 -0
  28. mint_sdk/add_field_specs.py +112 -0
  29. mint_sdk/add_frontend_commands.py +106 -0
  30. mint_sdk/add_frontend_routes.py +228 -0
  31. mint_sdk/add_naming.py +20 -0
  32. mint_sdk/add_project.py +61 -0
  33. mint_sdk/add_r_analysis_command.py +119 -0
  34. mint_sdk/add_r_templates.py +410 -0
  35. mint_sdk/add_source_edits.py +240 -0
  36. mint_sdk/add_template_catalog.py +88 -0
  37. mint_sdk/ai_instructions.md.template +310 -0
  38. mint_sdk/app.py +264 -0
  39. mint_sdk/cli.py +385 -0
  40. mint_sdk/cli_build.py +226 -0
  41. mint_sdk/cli_build_cmd.py +67 -0
  42. mint_sdk/cli_commands/__init__.py +0 -0
  43. mint_sdk/cli_commands/_utils.py +40 -0
  44. mint_sdk/cli_commands/add_scaffold_cmd.py +203 -0
  45. mint_sdk/cli_commands/add_templates_cmd.py +138 -0
  46. mint_sdk/cli_commands/admin_cmd.py +459 -0
  47. mint_sdk/cli_commands/auth_cmd.py +116 -0
  48. mint_sdk/cli_commands/debug_cmd.py +263 -0
  49. mint_sdk/cli_commands/experiment_cmd.py +288 -0
  50. mint_sdk/cli_commands/init_cmd.py +77 -0
  51. mint_sdk/cli_commands/plugin_cmd.py +320 -0
  52. mint_sdk/cli_commands/project_cmd.py +211 -0
  53. mint_sdk/cli_commands/status_cmd.py +68 -0
  54. mint_sdk/cli_commands/status_formatting.py +21 -0
  55. mint_sdk/cli_data_templates.py +166 -0
  56. mint_sdk/cli_init_catalog.py +47 -0
  57. mint_sdk/client/__init__.py +55 -0
  58. mint_sdk/client/_config.py +159 -0
  59. mint_sdk/client/_exceptions.py +130 -0
  60. mint_sdk/client/_http.py +147 -0
  61. mint_sdk/client/_types.py +90 -0
  62. mint_sdk/client/client.py +140 -0
  63. mint_sdk/client/resources/__init__.py +0 -0
  64. mint_sdk/client/resources/admin.py +166 -0
  65. mint_sdk/client/resources/auth.py +75 -0
  66. mint_sdk/client/resources/experiments.py +277 -0
  67. mint_sdk/client/resources/plugins.py +79 -0
  68. mint_sdk/client/resources/projects.py +82 -0
  69. mint_sdk/client/resources/updates.py +60 -0
  70. mint_sdk/context.py +103 -0
  71. mint_sdk/contract.py +388 -0
  72. mint_sdk/contract_ts_types.py +182 -0
  73. mint_sdk/contract_typescript.py +361 -0
  74. mint_sdk/deprecated_api_catalog.py +437 -0
  75. mint_sdk/deprecated_apis.py +77 -0
  76. mint_sdk/deprecated_frontend_destructure_patterns.py +103 -0
  77. mint_sdk/deprecated_frontend_doc_text_patterns.py +78 -0
  78. mint_sdk/deprecated_frontend_expression_patterns.py +126 -0
  79. mint_sdk/deprecated_frontend_import_patterns.py +157 -0
  80. mint_sdk/deprecated_frontend_tag_patterns.py +191 -0
  81. mint_sdk/deps_command.py +271 -0
  82. mint_sdk/dev_command.py +334 -0
  83. mint_sdk/dev_proxy_config.py +178 -0
  84. mint_sdk/docs/__init__.py +1 -0
  85. mint_sdk/docs/bundled/frontend.json +16845 -0
  86. mint_sdk/docs/cache.py +135 -0
  87. mint_sdk/docs/contract_docs.py +298 -0
  88. mint_sdk/docs/formatter.py +356 -0
  89. mint_sdk/docs/frontend_component_catalog.py +160 -0
  90. mint_sdk/docs/frontend_component_chooser.py +290 -0
  91. mint_sdk/docs/frontend_css_formatters.py +88 -0
  92. mint_sdk/docs/frontend_detail_formatters.py +237 -0
  93. mint_sdk/docs/frontend_extractor.py +79 -0
  94. mint_sdk/docs/frontend_usage_snippets.py +331 -0
  95. mint_sdk/docs/manifest_lookup.py +87 -0
  96. mint_sdk/docs/python_detail_formatters.py +197 -0
  97. mint_sdk/docs/python_extractor.py +324 -0
  98. mint_sdk/docs/python_introspection.py +121 -0
  99. mint_sdk/docs/reference_docs.py +453 -0
  100. mint_sdk/docs/reference_guides.py +306 -0
  101. mint_sdk/docs/reference_search.py +291 -0
  102. mint_sdk/docs/search.py +235 -0
  103. mint_sdk/docs/suggestions.py +23 -0
  104. mint_sdk/docs/template_data.py +24 -0
  105. mint_sdk/docs/template_docs.py +316 -0
  106. mint_sdk/docs/template_search.py +218 -0
  107. mint_sdk/docs/template_usage.py +126 -0
  108. mint_sdk/docs/terminal_style.py +44 -0
  109. mint_sdk/docs/topic_routes.py +135 -0
  110. mint_sdk/docs_command.py +303 -0
  111. mint_sdk/doctor_command.py +374 -0
  112. mint_sdk/doctor_deprecated_api_usage.py +184 -0
  113. mint_sdk/doctor_deprecated_frontend.py +210 -0
  114. mint_sdk/doctor_deprecated_python.py +23 -0
  115. mint_sdk/doctor_fixes.py +115 -0
  116. mint_sdk/doctor_frontend_checks.py +261 -0
  117. mint_sdk/doctor_frontend_component_guidance.py +230 -0
  118. mint_sdk/doctor_frontend_component_patterns.py +79 -0
  119. mint_sdk/doctor_frontend_guidance.py +188 -0
  120. mint_sdk/doctor_frontend_import_guidance.py +102 -0
  121. mint_sdk/doctor_frontend_sdk_catalog.py +364 -0
  122. mint_sdk/doctor_frontend_sdk_patterns.py +95 -0
  123. mint_sdk/doctor_frontend_sidebar_guidance.py +199 -0
  124. mint_sdk/doctor_frontend_source_utils.py +31 -0
  125. mint_sdk/doctor_guidance.py +77 -0
  126. mint_sdk/doctor_models.py +85 -0
  127. mint_sdk/doctor_output.py +110 -0
  128. mint_sdk/doctor_plugin_metadata.py +346 -0
  129. mint_sdk/doctor_r_command.py +47 -0
  130. mint_sdk/doctor_scan_config.py +63 -0
  131. mint_sdk/doctor_sdk_checks.py +90 -0
  132. mint_sdk/doctor_source_files.py +81 -0
  133. mint_sdk/exceptions.py +311 -0
  134. mint_sdk/export.py +177 -0
  135. mint_sdk/export_common.py +69 -0
  136. mint_sdk/export_tree.py +180 -0
  137. mint_sdk/info_command.py +74 -0
  138. mint_sdk/init_ai.py +67 -0
  139. mint_sdk/init_backend_templates.py +480 -0
  140. mint_sdk/init_command.py +369 -0
  141. mint_sdk/init_files.py +182 -0
  142. mint_sdk/init_frontend_templates.py +346 -0
  143. mint_sdk/init_naming.py +49 -0
  144. mint_sdk/init_post_scaffold.py +102 -0
  145. mint_sdk/init_script_templates.py +73 -0
  146. mint_sdk/init_template_catalog.py +230 -0
  147. mint_sdk/init_templates.py +74 -0
  148. mint_sdk/init_versions.py +77 -0
  149. mint_sdk/init_workflow_templates.py +131 -0
  150. mint_sdk/instrument.py +235 -0
  151. mint_sdk/jobs.py +398 -0
  152. mint_sdk/lcms.py +408 -0
  153. mint_sdk/link_command.py +218 -0
  154. mint_sdk/local_database.py +158 -0
  155. mint_sdk/logging.py +49 -0
  156. mint_sdk/logs_command.py +137 -0
  157. mint_sdk/migrations/__init__.py +22 -0
  158. mint_sdk/migrations/base.py +54 -0
  159. mint_sdk/migrations/errors.py +54 -0
  160. mint_sdk/migrations/locking.py +66 -0
  161. mint_sdk/migrations/ops.py +328 -0
  162. mint_sdk/migrations/runner.py +321 -0
  163. mint_sdk/models.py +73 -0
  164. mint_sdk/permissions.py +160 -0
  165. mint_sdk/plugin.py +210 -0
  166. mint_sdk/plugin_exports.py +27 -0
  167. mint_sdk/plugin_frontend.py +65 -0
  168. mint_sdk/plugin_lifecycle.py +48 -0
  169. mint_sdk/plugin_persistence.py +252 -0
  170. mint_sdk/plugin_settings.py +99 -0
  171. mint_sdk/py.typed +0 -0
  172. mint_sdk/r.py +374 -0
  173. mint_sdk/remote_context.py +391 -0
  174. mint_sdk/repositories.py +273 -0
  175. mint_sdk/schema.py +176 -0
  176. mint_sdk/sdk_generate_command.py +71 -0
  177. mint_sdk/templates/__init__.py +221 -0
  178. mint_sdk/templates/assay_matrix.py +214 -0
  179. mint_sdk/templates/base.py +358 -0
  180. mint_sdk/templates/calibration_curve.py +248 -0
  181. mint_sdk/templates/catalog.py +326 -0
  182. mint_sdk/templates/dose_response.py +125 -0
  183. mint_sdk/templates/flow_cytometry_panel.py +174 -0
  184. mint_sdk/templates/instrument_run.py +252 -0
  185. mint_sdk/templates/packs.py +164 -0
  186. mint_sdk/templates/plate_map.py +212 -0
  187. mint_sdk/templates/preset_catalog.py +266 -0
  188. mint_sdk/templates/preset_helpers.py +167 -0
  189. mint_sdk/templates/presets.py +408 -0
  190. mint_sdk/templates/protocol_steps.py +107 -0
  191. mint_sdk/templates/qpcr_plate.py +254 -0
  192. mint_sdk/templates/reagent_list.py +101 -0
  193. mint_sdk/templates/sample_prep.py +180 -0
  194. mint_sdk/templates/sample_sheet.py +158 -0
  195. mint_sdk/templates/time_course.py +189 -0
  196. mint_sdk/testing/__init__.py +36 -0
  197. mint_sdk/testing/plugins.py +135 -0
  198. mint_sdk/testing/recording_context.py +185 -0
  199. mint_sdk/testing/subprocess.py +88 -0
  200. mint_sdk/update_command.py +255 -0
  201. mint_sdk-1.0.0.dist-info/METADATA +197 -0
  202. mint_sdk-1.0.0.dist-info/RECORD +204 -0
  203. mint_sdk-1.0.0.dist-info/WHEEL +4 -0
  204. mint_sdk-1.0.0.dist-info/entry_points.txt +2 -0
mint_sdk/__init__.py ADDED
@@ -0,0 +1,354 @@
1
+ """
2
+ MINT Plugin SDK
3
+
4
+ SDK for building analysis plugins that integrate with the MINT platform.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from importlib import import_module
10
+
11
+ try:
12
+ from mint_sdk._version import __version__
13
+ except ImportError:
14
+ __version__ = "0.0.0"
15
+
16
+ _LAZY_EXPORTS = {
17
+ # API Client
18
+ "MINTClient": ("mint_sdk.client", "MINTClient"),
19
+ # Core plugin classes
20
+ "AnalysisPlugin": ("mint_sdk.plugin", "AnalysisPlugin"),
21
+ "PluginMetadata": ("mint_sdk.models", "PluginMetadata"),
22
+ "PluginCapabilities": ("mint_sdk.models", "PluginCapabilities"),
23
+ "PluginNavItem": ("mint_sdk.models", "PluginNavItem"),
24
+ "PluginType": ("mint_sdk.models", "PluginType"),
25
+ "PlatformContext": ("mint_sdk.context", "PlatformContext"),
26
+ # Lifecycle types
27
+ "HealthStatus": ("mint_sdk.plugin_lifecycle", "HealthStatus"),
28
+ "PluginHealth": ("mint_sdk.plugin_lifecycle", "PluginHealth"),
29
+ "LifecycleHookResult": ("mint_sdk.plugin_lifecycle", "LifecycleHookResult"),
30
+ # Exceptions
31
+ "PluginException": ("mint_sdk.exceptions", "PluginException"),
32
+ "ValidationException": ("mint_sdk.exceptions", "ValidationException"),
33
+ "PermissionException": ("mint_sdk.exceptions", "PermissionException"),
34
+ "ConfigurationException": ("mint_sdk.exceptions", "ConfigurationException"),
35
+ "RepositoryException": ("mint_sdk.exceptions", "RepositoryException"),
36
+ "NotFoundException": ("mint_sdk.exceptions", "NotFoundException"),
37
+ "ConflictException": ("mint_sdk.exceptions", "ConflictException"),
38
+ "PluginLifecycleException": ("mint_sdk.exceptions", "PluginLifecycleException"),
39
+ # Local database
40
+ "LocalDatabase": ("mint_sdk.local_database", "LocalDatabase"),
41
+ "LocalDatabaseConfig": ("mint_sdk.local_database", "LocalDatabaseConfig"),
42
+ # Jobs
43
+ "JobProgress": ("mint_sdk.jobs", "JobProgress"),
44
+ "JobId": ("mint_sdk.jobs", "JobId"),
45
+ "JobRegistry": ("mint_sdk.jobs", "JobRegistry"),
46
+ "JobState": ("mint_sdk.jobs", "JobState"),
47
+ "JobStatus": ("mint_sdk.jobs", "JobStatus"),
48
+ "TERMINAL_JOB_STATUSES": ("mint_sdk.jobs", "TERMINAL_JOB_STATUSES"),
49
+ # Data models
50
+ "Experiment": ("mint_sdk.repositories", "Experiment"),
51
+ "DesignData": ("mint_sdk.repositories", "DesignData"),
52
+ "PluginAnalysisResult": ("mint_sdk.repositories", "PluginAnalysisResult"),
53
+ "User": ("mint_sdk.repositories", "User"),
54
+ "UserPluginRole": ("mint_sdk.repositories", "UserPluginRole"),
55
+ # Repository protocols
56
+ "ExperimentRepository": ("mint_sdk.repositories", "ExperimentRepository"),
57
+ "PluginDataRepository": ("mint_sdk.repositories", "PluginDataRepository"),
58
+ "PluginRoleRepository": ("mint_sdk.repositories", "PluginRoleRepository"),
59
+ "UserRepository": ("mint_sdk.repositories", "UserRepository"),
60
+ "PlatformConfig": ("mint_sdk.repositories", "PlatformConfig"),
61
+ # Export utilities
62
+ "auto_json_to_tree": ("mint_sdk.export", "auto_json_to_tree"),
63
+ "auto_json_to_csv": ("mint_sdk.export", "auto_json_to_csv"),
64
+ "auto_json_to_summary": ("mint_sdk.export", "auto_json_to_summary"),
65
+ # Logging
66
+ "get_plugin_logger": ("mint_sdk.logging", "get_plugin_logger"),
67
+ # Schema conversion
68
+ "settings_model_to_form_fields": ("mint_sdk.schema", "settings_model_to_form_fields"),
69
+ "settings_model_to_settings_schema": ("mint_sdk.schema", "settings_model_to_settings_schema"),
70
+ # Permission helpers
71
+ "ADMIN_ROLE": ("mint_sdk.permissions", "ADMIN_ROLE"),
72
+ "ADMIN_PANEL_PERMISSIONS": ("mint_sdk.permissions", "ADMIN_PANEL_PERMISSIONS"),
73
+ "can_access_admin": ("mint_sdk.permissions", "can_access_admin"),
74
+ "can_access_plugin": ("mint_sdk.permissions", "can_access_plugin"),
75
+ "can_access_policy": ("mint_sdk.permissions", "can_access_policy"),
76
+ "get_access_audience": ("mint_sdk.permissions", "get_access_audience"),
77
+ "get_user_permissions": ("mint_sdk.permissions", "get_user_permissions"),
78
+ "has_all_permissions": ("mint_sdk.permissions", "has_all_permissions"),
79
+ "has_any_permission": ("mint_sdk.permissions", "has_any_permission"),
80
+ "is_admin_role": ("mint_sdk.permissions", "is_admin_role"),
81
+ "is_admin_user": ("mint_sdk.permissions", "is_admin_user"),
82
+ # Instrument monitoring models
83
+ "AlertLevel": ("mint_sdk.instrument", "AlertLevel"),
84
+ "InstrumentAlert": ("mint_sdk.instrument", "InstrumentAlert"),
85
+ "InstrumentAlertBody": ("mint_sdk.instrument", "InstrumentAlertBody"),
86
+ "InstrumentState": ("mint_sdk.instrument", "InstrumentState"),
87
+ "InstrumentStatus": ("mint_sdk.instrument", "InstrumentStatus"),
88
+ "SampleInfo": ("mint_sdk.instrument", "SampleInfo"),
89
+ "SequenceProgress": ("mint_sdk.instrument", "SequenceProgress"),
90
+ "build_sequence_progress": ("mint_sdk.instrument", "build_sequence_progress"),
91
+ # LCMS sequence helpers
92
+ "LcmsContainerType": ("mint_sdk.lcms", "LcmsContainerType"),
93
+ "LcmsMethodPathEntry": ("mint_sdk.lcms", "LcmsMethodPathEntry"),
94
+ "LcmsPlateCell": ("mint_sdk.lcms", "LcmsPlateCell"),
95
+ "LcmsPlateType": ("mint_sdk.lcms", "LcmsPlateType"),
96
+ "LcmsPolarity": ("mint_sdk.lcms", "LcmsPolarity"),
97
+ "LcmsSequenceItem": ("mint_sdk.lcms", "LcmsSequenceItem"),
98
+ "LcmsSequenceParams": ("mint_sdk.lcms", "LcmsSequenceParams"),
99
+ "combine_lcms_sequence_csvs": ("mint_sdk.lcms", "combine_lcms_sequence_csvs"),
100
+ "extract_lcms_common_prefix": ("mint_sdk.lcms", "extract_lcms_common_prefix"),
101
+ "extract_lcms_sample_name": ("mint_sdk.lcms", "extract_lcms_sample_name"),
102
+ "infer_lcms_plate_type_from_positions": ("mint_sdk.lcms", "infer_lcms_plate_type_from_positions"),
103
+ "insert_lcms_item_at_intervals": ("mint_sdk.lcms", "insert_lcms_item_at_intervals"),
104
+ "lcms_sequence_items_to_csv": ("mint_sdk.lcms", "lcms_sequence_items_to_csv"),
105
+ "lcms_well_id_from_position": ("mint_sdk.lcms", "lcms_well_id_from_position"),
106
+ "number_lcms_sequence_items": ("mint_sdk.lcms", "number_lcms_sequence_items"),
107
+ "parse_lcms_sequence_csv": ("mint_sdk.lcms", "parse_lcms_sequence_csv"),
108
+ "plate_cells_to_lcms_sequence_items": ("mint_sdk.lcms", "plate_cells_to_lcms_sequence_items"),
109
+ "reconstruct_lcms_plate_cells_from_sequence_items": (
110
+ "mint_sdk.lcms",
111
+ "reconstruct_lcms_plate_cells_from_sequence_items",
112
+ ),
113
+ "reorder_lcms_sequence_numbers": ("mint_sdk.lcms", "reorder_lcms_sequence_numbers"),
114
+ "resolve_lcms_method_path": ("mint_sdk.lcms", "resolve_lcms_method_path"),
115
+ # R bridge
116
+ "RAnalysisBridge": ("mint_sdk.r", "RAnalysisBridge"),
117
+ "RScriptSpec": ("mint_sdk.r", "RScriptSpec"),
118
+ "RBridgeError": ("mint_sdk.r", "RBridgeError"),
119
+ "RRunProvenance": ("mint_sdk.r", "RRunProvenance"),
120
+ # Bio data templates
121
+ "BioTemplateEnvelope": ("mint_sdk.templates", "BioTemplateEnvelope"),
122
+ "TEMPLATE_COLLECTION_KEY": ("mint_sdk.templates", "TEMPLATE_COLLECTION_KEY"),
123
+ "BioTemplateCatalogEntry": ("mint_sdk.templates", "BioTemplateCatalogEntry"),
124
+ "BioTemplatePackEntry": ("mint_sdk.templates", "BioTemplatePackEntry"),
125
+ "BioTemplatePresetEntry": ("mint_sdk.templates", "BioTemplatePresetEntry"),
126
+ "PlateMapTemplate": ("mint_sdk.templates", "PlateMapTemplate"),
127
+ "SampleSheetTemplate": ("mint_sdk.templates", "SampleSheetTemplate"),
128
+ "SamplePrepTemplate": ("mint_sdk.templates", "SamplePrepTemplate"),
129
+ "CalibrationCurveTemplate": ("mint_sdk.templates", "CalibrationCurveTemplate"),
130
+ "DoseResponseTemplate": ("mint_sdk.templates", "DoseResponseTemplate"),
131
+ "FlowCytometryPanelTemplate": ("mint_sdk.templates", "FlowCytometryPanelTemplate"),
132
+ "InstrumentRunTemplate": ("mint_sdk.templates", "InstrumentRunTemplate"),
133
+ "QpcrPlateTemplate": ("mint_sdk.templates", "QpcrPlateTemplate"),
134
+ "TimeCourseTemplate": ("mint_sdk.templates", "TimeCourseTemplate"),
135
+ "AssayMatrixTemplate": ("mint_sdk.templates", "AssayMatrixTemplate"),
136
+ "ReagentListTemplate": ("mint_sdk.templates", "ReagentListTemplate"),
137
+ "ProtocolStepsTemplate": ("mint_sdk.templates", "ProtocolStepsTemplate"),
138
+ "TemplateValidationError": ("mint_sdk.templates", "TemplateValidationError"),
139
+ "get_template_info": ("mint_sdk.templates", "get_template_info"),
140
+ "list_template_catalog": ("mint_sdk.templates", "list_template_catalog"),
141
+ "require_template_info": ("mint_sdk.templates", "require_template_info"),
142
+ "get_template_pack_info": ("mint_sdk.templates", "get_template_pack_info"),
143
+ "list_template_packs": ("mint_sdk.templates", "list_template_packs"),
144
+ "require_template_pack_info": ("mint_sdk.templates", "require_template_pack_info"),
145
+ "get_template_preset_info": ("mint_sdk.templates", "get_template_preset_info"),
146
+ "list_template_presets": ("mint_sdk.templates", "list_template_presets"),
147
+ "require_template_preset_info": ("mint_sdk.templates", "require_template_preset_info"),
148
+ "save_template_preset_collection": ("mint_sdk.templates", "save_template_preset_collection"),
149
+ "save_template": ("mint_sdk.templates", "save_template"),
150
+ "load_template": ("mint_sdk.templates", "load_template"),
151
+ "create_template_collection": ("mint_sdk.templates", "create_template_collection"),
152
+ "create_template_preset_collection": ("mint_sdk.templates", "create_template_preset_collection"),
153
+ "create_elisa_assay_collection": ("mint_sdk.templates", "create_elisa_assay_collection"),
154
+ "create_flow_cytometry_assay_collection": ("mint_sdk.templates", "create_flow_cytometry_assay_collection"),
155
+ "create_lcms_batch_collection": ("mint_sdk.templates", "create_lcms_batch_collection"),
156
+ "create_qpcr_expression_collection": ("mint_sdk.templates", "create_qpcr_expression_collection"),
157
+ "create_targeted_metabolomics_collection": (
158
+ "mint_sdk.templates",
159
+ "create_targeted_metabolomics_collection",
160
+ ),
161
+ "create_wellplate_screen_collection": ("mint_sdk.templates", "create_wellplate_screen_collection"),
162
+ "create_western_blot_assay_collection": ("mint_sdk.templates", "create_western_blot_assay_collection"),
163
+ "extract_template_collection": ("mint_sdk.templates", "extract_template_collection"),
164
+ "save_template_collection": ("mint_sdk.templates", "save_template_collection"),
165
+ "load_template_collection": ("mint_sdk.templates", "load_template_collection"),
166
+ # Migration framework
167
+ "MigrationOps": ("mint_sdk.migrations", "MigrationOps"),
168
+ "MigrationResult": ("mint_sdk.migrations", "MigrationResult"),
169
+ "MigrationRunner": ("mint_sdk.migrations", "MigrationRunner"),
170
+ "PluginMigration": ("mint_sdk.migrations", "PluginMigration"),
171
+ # App factory and helpers
172
+ "create_standalone_app": ("mint_sdk.app", "create_standalone_app"),
173
+ "SPAStaticFiles": ("mint_sdk.app", "SPAStaticFiles"),
174
+ "PluginDependency": ("mint_sdk.app", "PluginDependency"),
175
+ "require_context": ("mint_sdk.app", "require_context"),
176
+ }
177
+
178
+ __all__ = [
179
+ # API Client
180
+ "MINTClient",
181
+ # Core plugin classes
182
+ "AnalysisPlugin",
183
+ "PluginMetadata",
184
+ "PluginCapabilities",
185
+ "PluginNavItem",
186
+ "PluginType",
187
+ "PlatformContext",
188
+ # Lifecycle types
189
+ "HealthStatus",
190
+ "PluginHealth",
191
+ "LifecycleHookResult",
192
+ # Exceptions
193
+ "PluginException",
194
+ "ValidationException",
195
+ "PermissionException",
196
+ "ConfigurationException",
197
+ "RepositoryException",
198
+ "NotFoundException",
199
+ "ConflictException",
200
+ "PluginLifecycleException",
201
+ # Local database
202
+ "LocalDatabase",
203
+ "LocalDatabaseConfig",
204
+ # Jobs
205
+ "JobProgress",
206
+ "JobId",
207
+ "JobRegistry",
208
+ "JobState",
209
+ "JobStatus",
210
+ "TERMINAL_JOB_STATUSES",
211
+ # Data models
212
+ "Experiment",
213
+ "DesignData",
214
+ "PluginAnalysisResult",
215
+ "User",
216
+ "UserPluginRole",
217
+ # Repository protocols
218
+ "ExperimentRepository",
219
+ "PluginDataRepository",
220
+ "PluginRoleRepository",
221
+ "UserRepository",
222
+ "PlatformConfig",
223
+ # Export utilities
224
+ "auto_json_to_tree",
225
+ "auto_json_to_csv",
226
+ "auto_json_to_summary",
227
+ # Logging
228
+ "get_plugin_logger",
229
+ # Schema conversion
230
+ "settings_model_to_form_fields",
231
+ "settings_model_to_settings_schema",
232
+ # Permission helpers
233
+ "ADMIN_ROLE",
234
+ "ADMIN_PANEL_PERMISSIONS",
235
+ "can_access_admin",
236
+ "can_access_plugin",
237
+ "can_access_policy",
238
+ "get_access_audience",
239
+ "get_user_permissions",
240
+ "has_all_permissions",
241
+ "has_any_permission",
242
+ "is_admin_role",
243
+ "is_admin_user",
244
+ # Instrument monitoring models
245
+ "AlertLevel",
246
+ "InstrumentAlert",
247
+ "InstrumentAlertBody",
248
+ "InstrumentState",
249
+ "InstrumentStatus",
250
+ "SampleInfo",
251
+ "SequenceProgress",
252
+ "build_sequence_progress",
253
+ # LCMS sequence helpers
254
+ "LcmsContainerType",
255
+ "LcmsMethodPathEntry",
256
+ "LcmsPlateCell",
257
+ "LcmsPlateType",
258
+ "LcmsPolarity",
259
+ "LcmsSequenceItem",
260
+ "LcmsSequenceParams",
261
+ "combine_lcms_sequence_csvs",
262
+ "extract_lcms_common_prefix",
263
+ "extract_lcms_sample_name",
264
+ "infer_lcms_plate_type_from_positions",
265
+ "insert_lcms_item_at_intervals",
266
+ "lcms_sequence_items_to_csv",
267
+ "lcms_well_id_from_position",
268
+ "number_lcms_sequence_items",
269
+ "parse_lcms_sequence_csv",
270
+ "plate_cells_to_lcms_sequence_items",
271
+ "reconstruct_lcms_plate_cells_from_sequence_items",
272
+ "reorder_lcms_sequence_numbers",
273
+ "resolve_lcms_method_path",
274
+ # R bridge
275
+ "RAnalysisBridge",
276
+ "RScriptSpec",
277
+ "RBridgeError",
278
+ "RRunProvenance",
279
+ # Bio data templates
280
+ "BioTemplateEnvelope",
281
+ "TEMPLATE_COLLECTION_KEY",
282
+ "BioTemplateCatalogEntry",
283
+ "BioTemplatePackEntry",
284
+ "BioTemplatePresetEntry",
285
+ "PlateMapTemplate",
286
+ "SampleSheetTemplate",
287
+ "SamplePrepTemplate",
288
+ "CalibrationCurveTemplate",
289
+ "DoseResponseTemplate",
290
+ "FlowCytometryPanelTemplate",
291
+ "InstrumentRunTemplate",
292
+ "QpcrPlateTemplate",
293
+ "TimeCourseTemplate",
294
+ "AssayMatrixTemplate",
295
+ "ReagentListTemplate",
296
+ "ProtocolStepsTemplate",
297
+ "TemplateValidationError",
298
+ "get_template_info",
299
+ "list_template_catalog",
300
+ "require_template_info",
301
+ "get_template_pack_info",
302
+ "list_template_packs",
303
+ "require_template_pack_info",
304
+ "get_template_preset_info",
305
+ "list_template_presets",
306
+ "require_template_preset_info",
307
+ "save_template_preset_collection",
308
+ "save_template",
309
+ "load_template",
310
+ "create_template_collection",
311
+ "create_template_preset_collection",
312
+ "create_elisa_assay_collection",
313
+ "create_flow_cytometry_assay_collection",
314
+ "create_lcms_batch_collection",
315
+ "create_qpcr_expression_collection",
316
+ "create_targeted_metabolomics_collection",
317
+ "create_wellplate_screen_collection",
318
+ "create_western_blot_assay_collection",
319
+ "extract_template_collection",
320
+ "save_template_collection",
321
+ "load_template_collection",
322
+ # Migration framework
323
+ "MigrationOps",
324
+ "MigrationResult",
325
+ "MigrationRunner",
326
+ "PluginMigration",
327
+ # App factory and helpers
328
+ "create_standalone_app",
329
+ "SPAStaticFiles",
330
+ "PluginDependency",
331
+ "require_context",
332
+ ]
333
+
334
+
335
+ def __getattr__(name: str):
336
+ """Load public SDK symbols on first access.
337
+
338
+ Keeping package-root exports lazy avoids importing optional-heavy modules
339
+ such as SQLAlchemy and httpx during lightweight submodule imports like the
340
+ CLI entry point.
341
+ """
342
+ try:
343
+ module_name, attr_name = _LAZY_EXPORTS[name]
344
+ except KeyError as exc:
345
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from exc
346
+
347
+ module = import_module(module_name)
348
+ value = getattr(module, attr_name)
349
+ globals()[name] = value
350
+ return value
351
+
352
+
353
+ def __dir__() -> list[str]:
354
+ return sorted(set(globals()) | set(__all__))
mint_sdk/_discover.py ADDED
@@ -0,0 +1,313 @@
1
+ """Plugin discovery from pyproject.toml without importing the plugin.
2
+
3
+ Parses entry points, module paths, and routes prefix by inspecting files
4
+ statically — no plugin code is executed.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ import re
11
+ import sys
12
+ from collections.abc import Mapping
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ try:
18
+ import tomllib
19
+ except ModuleNotFoundError: # Python < 3.11
20
+ import tomli as tomllib # type: ignore[no-redef]
21
+
22
+
23
+ @dataclass(slots=True)
24
+ class DiscoveredPlugin:
25
+ """Statically discovered plugin metadata."""
26
+
27
+ entry_point_name: str # "drp-analysis"
28
+ module_path: str # "mint_plugin_drp.plugin"
29
+ class_name: str # "DRPAnalysisPlugin"
30
+ factory_target: str # "mint_plugin_drp.plugin:create_standalone_app"
31
+ routes_prefix: str # "/drp" (from source) or "/drp-analysis" (fallback)
32
+ project_name: str # "mint-plugin-drp"
33
+ project_version: str | None # "0.2.0" or None if dynamic
34
+ project_description: str # from [project].description
35
+ has_frontend: bool # frontend/package.json exists
36
+ metadata_name: str = "" # PluginMetadata.name, if statically readable
37
+ metadata_version: str = "" # PluginMetadata.version, if statically readable
38
+ metadata_description: str = "" # PluginMetadata.description, if statically readable
39
+ metadata_analysis_type: str = "" # PluginMetadata.analysis_type
40
+ metadata_plugin_type: str = "" # PluginMetadata.plugin_type
41
+ metadata_author: str = "" # PluginMetadata.author
42
+ metadata_homepage: str = "" # PluginMetadata.homepage
43
+ metadata_license: str = "" # PluginMetadata.license
44
+ metadata_icon: str = "" # PluginMetadata.icon
45
+ metadata_color: str = "" # PluginMetadata.color
46
+ metadata_nav_items: list[dict[str, Any]] | None = None
47
+
48
+
49
+ def _read_full_pyproject(project_dir: Path) -> dict:
50
+ """Read and return the full pyproject.toml as a dict."""
51
+ path = project_dir / "pyproject.toml"
52
+ if not path.exists():
53
+ print(f"Error: {path} not found.", file=sys.stderr)
54
+ sys.exit(1)
55
+ with open(path, "rb") as f:
56
+ return tomllib.load(f)
57
+
58
+
59
+ def _parse_entry_point(data: dict) -> tuple[str, str, str] | None:
60
+ """Extract (name, module_path, class_name) from [project.entry-points."mint.plugins"].
61
+
62
+ Returns None if no mint.plugins entry point is found.
63
+ """
64
+ entry_points = (
65
+ data.get("project", {}).get("entry-points", {}).get("mint.plugins", {})
66
+ )
67
+ if not entry_points:
68
+ return None
69
+
70
+ # Take the first entry point
71
+ name, value = next(iter(entry_points.items()))
72
+ # value is "mint_plugin_drp.plugin:DRPAnalysisPlugin"
73
+ if ":" not in value:
74
+ return None
75
+ module_path, class_name = value.rsplit(":", 1)
76
+ return name, module_path, class_name
77
+
78
+
79
+ def _module_source_candidates(module_path: str, project_dir: Path) -> list[Path]:
80
+ """Return possible source file paths for a plugin module path."""
81
+ parts = module_path.split(".")
82
+ return [
83
+ project_dir / "src" / Path(*parts).with_suffix(".py"),
84
+ project_dir / Path(*parts).with_suffix(".py"),
85
+ ]
86
+
87
+
88
+ def _read_module_source(module_path: str, project_dir: Path) -> str | None:
89
+ """Read a plugin module source file without importing it."""
90
+ for source_path in _module_source_candidates(module_path, project_dir):
91
+ if not source_path.exists():
92
+ continue
93
+ try:
94
+ return source_path.read_text()
95
+ except OSError:
96
+ continue
97
+ return None
98
+
99
+
100
+ def _parse_routes_prefix_from_source(module_path: str, project_dir: Path) -> str | None:
101
+ """Parse PLUGIN_ROUTES_PREFIX from the plugin source file without importing it.
102
+
103
+ Looks for patterns like:
104
+ PLUGIN_ROUTES_PREFIX = "/drp"
105
+ PLUGIN_ROUTES_PREFIX = '/drp'
106
+ """
107
+ content = _read_module_source(module_path, project_dir)
108
+ if content:
109
+ match = re.search(
110
+ r'PLUGIN_ROUTES_PREFIX\s*=\s*["\'](/[^"\']+)["\']', content
111
+ )
112
+ if match:
113
+ return match.group(1)
114
+
115
+ return None
116
+
117
+
118
+ def _is_call_named(node: ast.Call, name: str) -> bool:
119
+ func = node.func
120
+ if isinstance(func, ast.Name):
121
+ return func.id == name
122
+ return isinstance(func, ast.Attribute) and func.attr == name
123
+
124
+
125
+ def _literal_value(node: ast.AST, constants: Mapping[str, Any] | None = None) -> Any:
126
+ try:
127
+ return ast.literal_eval(node)
128
+ except (ValueError, SyntaxError):
129
+ if isinstance(node, ast.Attribute):
130
+ return node.attr.lower()
131
+ if isinstance(node, ast.Name) and constants and node.id in constants:
132
+ return constants[node.id]
133
+ return None
134
+
135
+
136
+ def _keyword_literal(
137
+ node: ast.Call,
138
+ name: str,
139
+ constants: Mapping[str, Any] | None = None,
140
+ ) -> Any:
141
+ for keyword in node.keywords:
142
+ if keyword.arg != name:
143
+ continue
144
+ return _literal_value(keyword.value, constants)
145
+ return None
146
+
147
+
148
+ def _metadata_string(
149
+ node: ast.Call,
150
+ name: str,
151
+ constants: Mapping[str, Any],
152
+ ) -> str:
153
+ value = _keyword_literal(node, name, constants)
154
+ return value if isinstance(value, str) else ""
155
+
156
+
157
+ def _parse_nav_items(node: ast.Call, constants: Mapping[str, Any]) -> list[dict[str, Any]]:
158
+ nav_items: list[dict[str, Any]] = []
159
+ for keyword in node.keywords:
160
+ if keyword.arg != "nav_items":
161
+ continue
162
+ if not isinstance(keyword.value, (ast.List, ast.Tuple)):
163
+ return nav_items
164
+ for item in keyword.value.elts:
165
+ if not isinstance(item, ast.Call) or not _is_call_named(item, "PluginNavItem"):
166
+ continue
167
+ nav_item: dict[str, Any] = {}
168
+ for key in (
169
+ "path",
170
+ "label",
171
+ "id",
172
+ "icon",
173
+ "description",
174
+ "requires_auth",
175
+ "requires_admin",
176
+ "requires_feature",
177
+ ):
178
+ value = _keyword_literal(item, key, constants)
179
+ if value not in (None, "", False):
180
+ nav_item[key] = value
181
+ if nav_item.get("path") and nav_item.get("label"):
182
+ nav_items.append(nav_item)
183
+ return nav_items
184
+
185
+
186
+ def _module_constants(tree: ast.Module) -> dict[str, Any]:
187
+ constants: dict[str, Any] = {}
188
+ for node in tree.body:
189
+ if isinstance(node, ast.Assign):
190
+ value = _literal_value(node.value, constants)
191
+ if value in (None, "", False):
192
+ continue
193
+ for target in node.targets:
194
+ if isinstance(target, ast.Name):
195
+ constants[target.id] = value
196
+ elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
197
+ value = _literal_value(node.value, constants) if node.value is not None else None
198
+ if value not in (None, "", False):
199
+ constants[node.target.id] = value
200
+ return constants
201
+
202
+
203
+ def _parse_metadata_from_source(module_path: str, project_dir: Path) -> dict[str, Any]:
204
+ """Parse simple PluginMetadata literal fields without importing the plugin."""
205
+ content = _read_module_source(module_path, project_dir)
206
+ if not content:
207
+ return {}
208
+ try:
209
+ tree = ast.parse(content)
210
+ except SyntaxError:
211
+ return {}
212
+ constants = _module_constants(tree)
213
+
214
+ for node in ast.walk(tree):
215
+ if not isinstance(node, ast.Call) or not _is_call_named(node, "PluginMetadata"):
216
+ continue
217
+ metadata: dict[str, Any] = {}
218
+ for key in (
219
+ "name",
220
+ "version",
221
+ "description",
222
+ "analysis_type",
223
+ "author",
224
+ "homepage",
225
+ "license",
226
+ "icon",
227
+ "color",
228
+ ):
229
+ if value := _metadata_string(node, key, constants):
230
+ metadata[key] = value
231
+ if plugin_type := _metadata_string(node, "plugin_type", constants):
232
+ metadata["plugin_type"] = plugin_type
233
+ if nav_items := _parse_nav_items(node, constants):
234
+ metadata["nav_items"] = nav_items
235
+ return metadata
236
+
237
+ return {}
238
+
239
+
240
+ def discover_plugin(project_dir: Path) -> DiscoveredPlugin:
241
+ """Discover plugin metadata from pyproject.toml and source files.
242
+
243
+ Raises SystemExit if the project directory lacks required configuration.
244
+ """
245
+ data = _read_full_pyproject(project_dir)
246
+
247
+ project = data.get("project")
248
+ if not project:
249
+ print("Error: pyproject.toml has no [project] table.", file=sys.stderr)
250
+ sys.exit(1)
251
+
252
+ entry_point = _parse_entry_point(data)
253
+ if not entry_point:
254
+ print(
255
+ "Error: No [project.entry-points.\"mint.plugins\"] found in pyproject.toml.",
256
+ file=sys.stderr,
257
+ )
258
+ sys.exit(1)
259
+
260
+ ep_name, module_path, class_name = entry_point
261
+
262
+ # Routes prefix: parse from source, fallback to /{entry_point_name}
263
+ routes_prefix = _parse_routes_prefix_from_source(module_path, project_dir)
264
+ if routes_prefix is None:
265
+ routes_prefix = f"/{ep_name}"
266
+
267
+ # Factory target for uvicorn --factory
268
+ factory_target = f"{module_path}:create_standalone_app"
269
+
270
+ # Version: static or None if dynamic
271
+ version = project.get("version")
272
+
273
+ has_frontend = (project_dir / "frontend" / "package.json").exists()
274
+ metadata = _parse_metadata_from_source(module_path, project_dir)
275
+
276
+ return DiscoveredPlugin(
277
+ entry_point_name=ep_name,
278
+ module_path=module_path,
279
+ class_name=class_name,
280
+ factory_target=factory_target,
281
+ routes_prefix=routes_prefix,
282
+ project_name=project["name"],
283
+ project_version=version,
284
+ project_description=project.get("description", ""),
285
+ has_frontend=has_frontend,
286
+ metadata_name=metadata.get("name", ""),
287
+ metadata_version=metadata.get("version", ""),
288
+ metadata_description=metadata.get("description", ""),
289
+ metadata_analysis_type=metadata.get("analysis_type", ""),
290
+ metadata_plugin_type=metadata.get("plugin_type", ""),
291
+ metadata_author=metadata.get("author", ""),
292
+ metadata_homepage=metadata.get("homepage", ""),
293
+ metadata_license=metadata.get("license", ""),
294
+ metadata_icon=metadata.get("icon", ""),
295
+ metadata_color=metadata.get("color", ""),
296
+ metadata_nav_items=metadata.get("nav_items"),
297
+ )
298
+
299
+
300
+ def get_sdk_dependency_spec(project_dir: Path) -> str | None:
301
+ """Extract the mint-sdk version specifier from pyproject.toml dependencies.
302
+
303
+ Returns e.g. ">=0.9.4" or None if mint-sdk is not in dependencies.
304
+ """
305
+ data = _read_full_pyproject(project_dir)
306
+ deps = data.get("project", {}).get("dependencies", [])
307
+ for dep in deps:
308
+ # Match "mint-sdk>=0.9.4", "mint-sdk>=0.9.4,<1.0", "mint-sdk"
309
+ match = re.match(r"mint[-_]sdk\s*(.*)", dep, re.IGNORECASE)
310
+ if match:
311
+ spec = match.group(1).strip()
312
+ return spec if spec else "*"
313
+ return None