PlatformContext
PlatformContext is the single object the platform hands to a plugin. Through it, the plugin reaches every platform-side service: experiments, plugin data, users, plugin roles, the platform config, and a database session scoped to the plugin's schema.
from mint_sdk import PlatformContext
class MyPlugin(AnalysisPlugin):
async def initialize(self, context: PlatformContext | None = None):
self._context = context # may be None in standalone modeWhen standalone, context is None; the plugin uses LocalDatabase (a mint-sdk-managed local SQLite) instead. When integrated, context is a real PlatformContext instance and every accessor below is live.
Accessors
| Accessor | Returns | Notes |
|---|---|---|
is_authenticated (property) | bool | True if the active request has an authenticated user |
get_current_user_dependency() | FastAPI Depends | Inject as user = Depends(context.get_current_user_dependency()) |
get_optional_user_dependency() | FastAPI Depends | As above but None-tolerant |
get_user_repository() | UserRepository | None | User lookups (read-only) |
get_experiment_repository() | ExperimentRepository | None | Experiment access, type-gated by PluginType |
get_plugin_data_repository() | PluginDataRepository | None | Save/load DesignData and PluginAnalysisResult |
get_plugin_role_repository() | PluginRoleRepository | None | Per-plugin user roles |
require_plugin_role(*roles) | FastAPI Depends | Route guard — see below |
get_config() | dict (PlatformConfig) | Platform configuration view (filtered) |
get_shared_db_session() | async context manager | Async SQLAlchemy session scoped to your plugin's schema |
Repository access is still bounded by the platform deployment and plugin type. STATIC and ANALYSIS get a read-only experiment wrapper, EXPERIMENT_DESIGN gets a design-scoped wrapper, and FULL gets full experiment access. get_shared_db_session() requires shared-database setup; declare requires_shared_database=True when your plugin owns tables.
Authentication and current user
Two FastAPI dependencies cover the common cases. Because routes are usually declared in separate modules and PlatformContext is attached during initialize(), build auth-dependent routers from the plugin instance:
from fastapi import APIRouter, Depends
from mint_sdk import PlatformContext
class MyPlugin(AnalysisPlugin):
async def initialize(self, context: PlatformContext | None = None):
self._context = context
def get_routers(self):
return [(create_router(self), "")]
async def _allow_standalone():
return None
def create_router(plugin: MyPlugin) -> APIRouter:
router = APIRouter()
context = getattr(plugin, "_context", None)
current_user = (
context.get_current_user_dependency()
if context is not None
else _allow_standalone
)
@router.get("/me")
async def me(user=Depends(current_user)):
return {"user": user}
return routerStandalone mode falls through to the stub. Integrated mode uses the platform auth dependency and request-scoped user context.
Plugin role guard
require_plugin_role(*roles) returns a dependency that:
- Resolves the current user via the same auth dependency
- Reads the user's plugin role from
PluginRoleRepository - Allows the request through only if the role is in
roles - Bypasses the check for platform admins automatically
from fastapi import APIRouter, Depends
async def _allow_standalone():
return None
def create_admin_router(plugin: MyPlugin) -> APIRouter:
router = APIRouter(tags=["admin"])
context = getattr(plugin, "_context", None)
admin_or_owner = (
context.require_plugin_role("admin", "owner")
if context is not None
else Depends(_allow_standalone)
)
@router.get("/admin/settings", dependencies=[admin_or_owner])
async def settings():
return {"settings": "..."}
return routerSee Recipes → Route permissions for the full pattern.
Database session
get_shared_db_session() is the canonical way for a plugin to talk to its own tables. The session has its search_path set to the plugin's schema, so unqualified table names resolve correctly:
from sqlalchemy import select
class MyPlugin(AnalysisPlugin):
async def list_panels(self):
async with self._context.get_shared_db_session() as session:
result = await session.execute(select(PanelModel))
return result.scalars().all()Standalone mode has its own equivalent — AnalysisPlugin.get_plugin_db_session() (an instance method on the plugin itself, not the context) routes to LocalDatabase when no context is present:
class MyPlugin(AnalysisPlugin):
async def list_panels(self):
async with self.get_plugin_db_session() as session:
... # works in both modesPrefer self.get_plugin_db_session() over the context method directly — it gives you mode-portable plugin code.
Convenience methods on AnalysisPlugin
For the most common operations on DesignData and PluginAnalysisResult, the plugin base class wraps PluginDataRepository:
| Method | What it does | Standalone? |
|---|---|---|
await self.save_design(experiment_id, data) | Save / update design data | Returns None |
await self.load_design(experiment_id) | Load design data | Returns None |
await self.save_analysis(experiment_id, result) | Save / update analysis result for this plugin | Returns None |
await self.load_analysis(experiment_id, fields=None) | Load analysis result for this plugin, optionally projecting top-level result keys | Returns None |
await self.load_artifacts(experiment_id) | Load only result["artifacts"] from this plugin's result | Returns None |
await self.load_analyses(experiment_id, include_others=False) | Load analysis results; defaults to this plugin's own result | Returns [] |
await self.save(experiment_id, design=..., analysis=...) | Save both at once | Returns (None, None) |
await self.load(experiment_id) | Load both | Returns (None, None) |
await self.delete_design(experiment_id) | Delete design | Returns False |
await self.delete_analysis(experiment_id) | Delete analysis | Returns False |
These are the daily authoring API. Drop down to context.get_plugin_data_repository() only when you need bulk operations or have multiple plugin IDs to coordinate.
For cross-plugin readers, call load_analyses(experiment_id, include_others=True) or repo.get_analysis_results(experiment_id, include_others=True) explicitly. The default is intentionally scoped to the calling plugin so ordinary analysis plugins do not accidentally consume another plugin's result payloads.
What PlatformContext is not
- Not a request-scoped object you
Depends-inject. It's a long-lived object set duringinitialize()and stored on the plugin instance. - Not a synchronous interface. Every accessor returning data uses async I/O. The shared-mode and isolated-mode variants both honor this.
- Not a container for user state. The user comes from the FastAPI auth dependency (
get_current_user_dependency()), not from the context object directly.
Standalone fallback pattern
For mode-portable code:
class MyPlugin(AnalysisPlugin):
async def initialize(self, context=None):
self._context = context
if context is None:
self._setup_standalone_db() # base class helper
def get_experiment_id_from_request(self, request_body):
# Use convenience methods — they no-op cleanly when standalone
return request_body.get("experiment_id", 1)
async def fetch(self, experiment_id):
# Works in both modes
return await self.load_design(experiment_id)The convenience methods (save_design, load_design, …) return None/False in standalone mode rather than raising — your plugin can carry on with empty results in development without branching code.
Next
→ Data model — what the repos return → Recipes → Reading experiments — concrete ExperimentRepository patterns → Recipes → Route permissions — using the plugin role guard