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.
- mint_sdk/__init__.py +354 -0
- mint_sdk/_discover.py +313 -0
- mint_sdk/_prompt.py +228 -0
- mint_sdk/_version.py +24 -0
- mint_sdk/add_backend_artifact.py +52 -0
- mint_sdk/add_backend_commands.py +356 -0
- mint_sdk/add_backend_endpoint.py +38 -0
- mint_sdk/add_backend_hook.py +56 -0
- mint_sdk/add_backend_job.py +145 -0
- mint_sdk/add_backend_migration.py +47 -0
- mint_sdk/add_backend_router.py +36 -0
- mint_sdk/add_backend_schema.py +42 -0
- mint_sdk/add_backend_service.py +56 -0
- mint_sdk/add_backend_setting.py +34 -0
- mint_sdk/add_backend_wiring.py +97 -0
- mint_sdk/add_command.py +67 -0
- mint_sdk/add_data_template_backend_templates.py +379 -0
- mint_sdk/add_data_template_commands.py +260 -0
- mint_sdk/add_data_template_frontend_assay_templates.py +364 -0
- mint_sdk/add_data_template_frontend_core_templates.py +194 -0
- mint_sdk/add_data_template_frontend_helpers.py +107 -0
- mint_sdk/add_data_template_frontend_lab_templates.py +201 -0
- mint_sdk/add_data_template_frontend_pack_templates.py +249 -0
- mint_sdk/add_data_template_frontend_panel_templates.py +199 -0
- mint_sdk/add_data_template_frontend_templates.py +145 -0
- mint_sdk/add_data_template_frontend_workflow_templates.py +206 -0
- mint_sdk/add_data_template_router_templates.py +295 -0
- mint_sdk/add_field_specs.py +112 -0
- mint_sdk/add_frontend_commands.py +106 -0
- mint_sdk/add_frontend_routes.py +228 -0
- mint_sdk/add_naming.py +20 -0
- mint_sdk/add_project.py +61 -0
- mint_sdk/add_r_analysis_command.py +119 -0
- mint_sdk/add_r_templates.py +410 -0
- mint_sdk/add_source_edits.py +240 -0
- mint_sdk/add_template_catalog.py +88 -0
- mint_sdk/ai_instructions.md.template +310 -0
- mint_sdk/app.py +264 -0
- mint_sdk/cli.py +385 -0
- mint_sdk/cli_build.py +226 -0
- mint_sdk/cli_build_cmd.py +67 -0
- mint_sdk/cli_commands/__init__.py +0 -0
- mint_sdk/cli_commands/_utils.py +40 -0
- mint_sdk/cli_commands/add_scaffold_cmd.py +203 -0
- mint_sdk/cli_commands/add_templates_cmd.py +138 -0
- mint_sdk/cli_commands/admin_cmd.py +459 -0
- mint_sdk/cli_commands/auth_cmd.py +116 -0
- mint_sdk/cli_commands/debug_cmd.py +263 -0
- mint_sdk/cli_commands/experiment_cmd.py +288 -0
- mint_sdk/cli_commands/init_cmd.py +77 -0
- mint_sdk/cli_commands/plugin_cmd.py +320 -0
- mint_sdk/cli_commands/project_cmd.py +211 -0
- mint_sdk/cli_commands/status_cmd.py +68 -0
- mint_sdk/cli_commands/status_formatting.py +21 -0
- mint_sdk/cli_data_templates.py +166 -0
- mint_sdk/cli_init_catalog.py +47 -0
- mint_sdk/client/__init__.py +55 -0
- mint_sdk/client/_config.py +159 -0
- mint_sdk/client/_exceptions.py +130 -0
- mint_sdk/client/_http.py +147 -0
- mint_sdk/client/_types.py +90 -0
- mint_sdk/client/client.py +140 -0
- mint_sdk/client/resources/__init__.py +0 -0
- mint_sdk/client/resources/admin.py +166 -0
- mint_sdk/client/resources/auth.py +75 -0
- mint_sdk/client/resources/experiments.py +277 -0
- mint_sdk/client/resources/plugins.py +79 -0
- mint_sdk/client/resources/projects.py +82 -0
- mint_sdk/client/resources/updates.py +60 -0
- mint_sdk/context.py +103 -0
- mint_sdk/contract.py +388 -0
- mint_sdk/contract_ts_types.py +182 -0
- mint_sdk/contract_typescript.py +361 -0
- mint_sdk/deprecated_api_catalog.py +437 -0
- mint_sdk/deprecated_apis.py +77 -0
- mint_sdk/deprecated_frontend_destructure_patterns.py +103 -0
- mint_sdk/deprecated_frontend_doc_text_patterns.py +78 -0
- mint_sdk/deprecated_frontend_expression_patterns.py +126 -0
- mint_sdk/deprecated_frontend_import_patterns.py +157 -0
- mint_sdk/deprecated_frontend_tag_patterns.py +191 -0
- mint_sdk/deps_command.py +271 -0
- mint_sdk/dev_command.py +334 -0
- mint_sdk/dev_proxy_config.py +178 -0
- mint_sdk/docs/__init__.py +1 -0
- mint_sdk/docs/bundled/frontend.json +16845 -0
- mint_sdk/docs/cache.py +135 -0
- mint_sdk/docs/contract_docs.py +298 -0
- mint_sdk/docs/formatter.py +356 -0
- mint_sdk/docs/frontend_component_catalog.py +160 -0
- mint_sdk/docs/frontend_component_chooser.py +290 -0
- mint_sdk/docs/frontend_css_formatters.py +88 -0
- mint_sdk/docs/frontend_detail_formatters.py +237 -0
- mint_sdk/docs/frontend_extractor.py +79 -0
- mint_sdk/docs/frontend_usage_snippets.py +331 -0
- mint_sdk/docs/manifest_lookup.py +87 -0
- mint_sdk/docs/python_detail_formatters.py +197 -0
- mint_sdk/docs/python_extractor.py +324 -0
- mint_sdk/docs/python_introspection.py +121 -0
- mint_sdk/docs/reference_docs.py +453 -0
- mint_sdk/docs/reference_guides.py +306 -0
- mint_sdk/docs/reference_search.py +291 -0
- mint_sdk/docs/search.py +235 -0
- mint_sdk/docs/suggestions.py +23 -0
- mint_sdk/docs/template_data.py +24 -0
- mint_sdk/docs/template_docs.py +316 -0
- mint_sdk/docs/template_search.py +218 -0
- mint_sdk/docs/template_usage.py +126 -0
- mint_sdk/docs/terminal_style.py +44 -0
- mint_sdk/docs/topic_routes.py +135 -0
- mint_sdk/docs_command.py +303 -0
- mint_sdk/doctor_command.py +374 -0
- mint_sdk/doctor_deprecated_api_usage.py +184 -0
- mint_sdk/doctor_deprecated_frontend.py +210 -0
- mint_sdk/doctor_deprecated_python.py +23 -0
- mint_sdk/doctor_fixes.py +115 -0
- mint_sdk/doctor_frontend_checks.py +261 -0
- mint_sdk/doctor_frontend_component_guidance.py +230 -0
- mint_sdk/doctor_frontend_component_patterns.py +79 -0
- mint_sdk/doctor_frontend_guidance.py +188 -0
- mint_sdk/doctor_frontend_import_guidance.py +102 -0
- mint_sdk/doctor_frontend_sdk_catalog.py +364 -0
- mint_sdk/doctor_frontend_sdk_patterns.py +95 -0
- mint_sdk/doctor_frontend_sidebar_guidance.py +199 -0
- mint_sdk/doctor_frontend_source_utils.py +31 -0
- mint_sdk/doctor_guidance.py +77 -0
- mint_sdk/doctor_models.py +85 -0
- mint_sdk/doctor_output.py +110 -0
- mint_sdk/doctor_plugin_metadata.py +346 -0
- mint_sdk/doctor_r_command.py +47 -0
- mint_sdk/doctor_scan_config.py +63 -0
- mint_sdk/doctor_sdk_checks.py +90 -0
- mint_sdk/doctor_source_files.py +81 -0
- mint_sdk/exceptions.py +311 -0
- mint_sdk/export.py +177 -0
- mint_sdk/export_common.py +69 -0
- mint_sdk/export_tree.py +180 -0
- mint_sdk/info_command.py +74 -0
- mint_sdk/init_ai.py +67 -0
- mint_sdk/init_backend_templates.py +480 -0
- mint_sdk/init_command.py +369 -0
- mint_sdk/init_files.py +182 -0
- mint_sdk/init_frontend_templates.py +346 -0
- mint_sdk/init_naming.py +49 -0
- mint_sdk/init_post_scaffold.py +102 -0
- mint_sdk/init_script_templates.py +73 -0
- mint_sdk/init_template_catalog.py +230 -0
- mint_sdk/init_templates.py +74 -0
- mint_sdk/init_versions.py +77 -0
- mint_sdk/init_workflow_templates.py +131 -0
- mint_sdk/instrument.py +235 -0
- mint_sdk/jobs.py +398 -0
- mint_sdk/lcms.py +408 -0
- mint_sdk/link_command.py +218 -0
- mint_sdk/local_database.py +158 -0
- mint_sdk/logging.py +49 -0
- mint_sdk/logs_command.py +137 -0
- mint_sdk/migrations/__init__.py +22 -0
- mint_sdk/migrations/base.py +54 -0
- mint_sdk/migrations/errors.py +54 -0
- mint_sdk/migrations/locking.py +66 -0
- mint_sdk/migrations/ops.py +328 -0
- mint_sdk/migrations/runner.py +321 -0
- mint_sdk/models.py +73 -0
- mint_sdk/permissions.py +160 -0
- mint_sdk/plugin.py +210 -0
- mint_sdk/plugin_exports.py +27 -0
- mint_sdk/plugin_frontend.py +65 -0
- mint_sdk/plugin_lifecycle.py +48 -0
- mint_sdk/plugin_persistence.py +252 -0
- mint_sdk/plugin_settings.py +99 -0
- mint_sdk/py.typed +0 -0
- mint_sdk/r.py +374 -0
- mint_sdk/remote_context.py +391 -0
- mint_sdk/repositories.py +273 -0
- mint_sdk/schema.py +176 -0
- mint_sdk/sdk_generate_command.py +71 -0
- mint_sdk/templates/__init__.py +221 -0
- mint_sdk/templates/assay_matrix.py +214 -0
- mint_sdk/templates/base.py +358 -0
- mint_sdk/templates/calibration_curve.py +248 -0
- mint_sdk/templates/catalog.py +326 -0
- mint_sdk/templates/dose_response.py +125 -0
- mint_sdk/templates/flow_cytometry_panel.py +174 -0
- mint_sdk/templates/instrument_run.py +252 -0
- mint_sdk/templates/packs.py +164 -0
- mint_sdk/templates/plate_map.py +212 -0
- mint_sdk/templates/preset_catalog.py +266 -0
- mint_sdk/templates/preset_helpers.py +167 -0
- mint_sdk/templates/presets.py +408 -0
- mint_sdk/templates/protocol_steps.py +107 -0
- mint_sdk/templates/qpcr_plate.py +254 -0
- mint_sdk/templates/reagent_list.py +101 -0
- mint_sdk/templates/sample_prep.py +180 -0
- mint_sdk/templates/sample_sheet.py +158 -0
- mint_sdk/templates/time_course.py +189 -0
- mint_sdk/testing/__init__.py +36 -0
- mint_sdk/testing/plugins.py +135 -0
- mint_sdk/testing/recording_context.py +185 -0
- mint_sdk/testing/subprocess.py +88 -0
- mint_sdk/update_command.py +255 -0
- mint_sdk-1.0.0.dist-info/METADATA +197 -0
- mint_sdk-1.0.0.dist-info/RECORD +204 -0
- mint_sdk-1.0.0.dist-info/WHEEL +4 -0
- 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
|