This release is a pre-release and may not be stable for production use.
MINT SDK (Python)
SDK for building analysis plugins that integrate with the MINT platform.
Full Documentation: See the comprehensive docs for detailed API reference and guides.
Installation
# From PyPI (when published)
uv add mint-sdk
# From git
uv add git+https://github.com/MorscherLab/MINT#subdirectory=packages/sdk-python
MINT SDK 1.1 requires Pydantic >=2.12.0. New mint init projects include
that minimum automatically.
Quick Start
Create a Python-only plugin whose standard UI is supplied by the SDK:
mint init my-plugin --mode generated --yes
cd my-plugin
mint dev
Generated mode uses typed Pydantic inputs with @generated_ui() and @job.
It does not create a Vue project and mint build does not
require Bun.
Use standard mode when the plugin needs a custom Vue page:
mint init my-plugin --mode standard --yes
cd my-plugin
mint sdk generate
mint dev
Standard mode starts with a small PluginWorkspaceView, a working form, and a
generated typed client. That workspace is starter code, not a required layout;
it can be replaced or removed without making mint doctor reject the mode.
mint init --yes defaults to generated mode. Pass --mode standard when the
plugin needs a custom Vue frontend.
mint doctor --strict is a pre-commit and CI validation step, not a startup
requirement for mint dev.
mint sdk generate writes the frontend contract and typed client from backend routes and Pydantic schemas, so Vue code can call plugin endpoints without hand-writing route prefixes or request/response types.
Use mint docs contract inside a plugin to inspect the generated endpoint and client-call contract without writing files.
Use mint sdk generate --check --json in CI or editor tasks when you need machine-readable drift status.
Use mint doctor --json for machine-readable project health checks and safe-fix status.
When adding backend pieces, pass --generate to supported mint add commands to refresh the generated client in the same step.
Declarative plugin contracts
New plugins keep metadata and lifecycle hooks explicit with decorators while
remaining regular AnalysisPlugin subclasses:
from pydantic import BaseModel
from mint_sdk import (
AnalysisPlugin,
ConfigChange,
PluginHealth,
health_check,
mint_plugin,
on_config_change,
)
class PeakQcSettings(BaseModel):
threshold: float = 0.05
@mint_plugin(
analysis_type="metabolomics",
routes_prefix="/peak-qc",
config=PeakQcSettings,
)
class PeakQcPlugin(AnalysisPlugin):
def get_routers(self):
return []
async def initialize(self, context=None) -> None:
self._context = context
async def shutdown(self) -> None:
pass
@health_check(timeout=2.0)
async def health(self) -> PluginHealth:
return PluginHealth(message="ready")
@on_config_change(fields={"threshold"})
def threshold_changed(self, change: ConfigChange[PeakQcSettings]) -> None:
self._threshold = change.current.threshold
Package identity comes from PEP 621 metadata and the sole mint.plugins
entry point. @mint_plugin declares only runtime behavior. @health_check
accepts sync or async handlers; sync
handlers run in a worker thread, and timeouts or exceptions produce an
unhealthy result. @on_config_change handlers are synchronous, run after the
settings commit in declaration order, and receive only actual top-level field
changes. Existing method-based plugins remain compatible, but a plugin must not
declare both styles for the same contract.
Notifications and calendar feeds
Async plugin methods may return typed notification or calendar values. The decorators validate each result and publish it when the plugin instance is bound to an integrated platform context:
from datetime import UTC, datetime
from mint_sdk import (
AnalysisPlugin,
CalendarEvent,
NotificationEvent,
NotificationSeverity,
calendar_event,
notify,
)
class InstrumentPlanner(AnalysisPlugin):
@notify(channels={"email", "teams"})
async def report_stop(self, run_id: str) -> NotificationEvent:
return NotificationEvent(
event_key=f"run:{run_id}:stopped",
severity=NotificationSeverity.CRITICAL,
title="Acquisition stopped",
message=f"Run {run_id} stopped before completion.",
occurred_at=datetime.now(UTC),
)
@calendar_event
async def schedule(
self,
run_id: str,
user_id: int,
start: datetime,
end: datetime,
) -> CalendarEvent:
return CalendarEvent(
event_key=f"run:{run_id}",
title=f"Run {run_id}",
start=start,
end=end,
participant_user_ids=(user_id,),
)
An instrument probe may call a normal, plugin-owned API and then invoke an
async @notify method. The plugin owns and validates the probe token; MINT
does not issue, store, rotate, or read it. MINT receives only the method's
structured notification result.
Only unacknowledged Critical notifications are delivered externally. MINT selects email recipients and owns the SMTP, Teams, and Slack targets. Calendar events use stable keys for upsert or cancellation and appear in read-only ICS feeds. Each decorator accepts at most 100 typed results per call.
An unbound or standalone direct call returns the method result without
publishing. An integrated host-bound direct call can publish. Stacking
@notify with @job requests completed/failed owner email only for a managed
Job; calling that function directly does not create a terminal Job email. See
the plugin guide
and 1.1 migration guide
for the full contracts.
Backend-only generated UI
Plugins with simple controls and results can omit frontend source. Declare an
optional Pydantic config model with @mint_plugin(config=...), calculations
with @job, and the plugin class with @generated_ui. MINT then serves a
standard workspace in standalone and integrated modes. An existing plugin Vue
frontend always takes precedence.
from mint_sdk import (
AnalysisPlugin,
TableResult,
generated_ui,
job,
mint_plugin,
)
from pydantic import BaseModel, Field
class Inputs(BaseModel):
threshold: float = Field(1.0, ge=0.0)
class PluginConfig(BaseModel):
method: str = "default"
@mint_plugin(
analysis_type="test",
routes_prefix="/simple-analysis",
config=PluginConfig,
)
@generated_ui(title="Simple analysis")
class Plugin(AnalysisPlugin):
@job(cpu=1)
def run(self, inputs: Inputs) -> TableResult:
return TableResult(
columns=["threshold"],
rows=[{"threshold": inputs.threshold}],
)
Input v1 supports typed scalar fields, Pydantic models, enums, toggles, string
arrays, and ordinary Path file/directory inputs. Result v1 supports text, JSON, tables/DataFrames,
images, artifacts, Matplotlib figures, and interactive Plotly figures.
Artifact v1 embeds its download bytes. Arbitrary HTML, JavaScript, and frontend
callbacks are rejected.
The generic workspace cannot infer domain-specific part batching.
@generated_ui therefore rejects a class containing
@job(profile=StagedJob(...)); use a standard/custom frontend and its generated
usePluginJobs() client for that lifecycle.
Call a decorated job like an ordinary synchronous Python method. The platform schedules the
same job through its HTTP routes. @job, JobContext, JobManager, and the result types are
stable SDK APIs.
JobContext.report() publishes percent/stage/message plus structured current-file progress;
JobContext.warn() records bounded non-fatal warnings without replacing the progress message.
The generated workspace schema is versioned but remains experimental in this minor release. See the
backend-only example.
Runtime configuration and concurrency
@mint_plugin(config=PluginConfig) makes the decorator the single config-model
declaration for standalone and integrated settings.
Effective values resolve in this order:
model defaults < saved store < explicit .env < process environment < startup override
Environment variables use
MINT_PLUGIN_<NORMALIZED_PLUGIN_NAME>__<FIELD>; nested fields add another
double underscore. For example,
MINT_PLUGIN_MY_ANALYSIS__DATABASE__HOST. Values supplied by .env, the
process environment, or startup overrides remain effective. Writes still
replace the saved backing value; that value appears in stored_settings and is
marked inactive until the higher-priority runtime override is removed. Secret
fields are never returned or persisted by the public settings API.
Use await self.save_settings_transactionally(candidate) when intentionally
replacing the full typed backing value. Use
await self.patch_settings_transactionally({"threshold": 0.1}) for a shallow
top-level patch. The patch reads the latest durable backing value and retries
compare-and-swap conflicts up to three times, so concurrent requests changing
different fields do not overwrite each other. Validation or preflight failure
does not change durable or in-memory state. A preflight may run again after a
conflict, so it must be repeatable and side-effect free; @on_config_change
handlers run once, only after a successful commit. Exhausted retries raise
SettingsConflictError with committed=False; a post-commit handler failure
uses SettingsTransactionError with committed=True.
The platform in-process host and the standalone JSON runtime attach an authoritative atomic store. A resolver-backed host without that boundary fails the patch closed instead of performing a best-effort read-modify-write. Legacy providers without an SDK resolver retain only process-local serialization through their existing persistence hook.
Config models use a strict, fail-fast storage contract. Supported fields are
named BaseModel fields composed from the standard scalar types, dates and
times, Decimal, UUID, Path, Enum, Pydantic secret types, nested
BaseModel values, explicit mappings/sequences, Literal, unions, and
Annotated with ordinary Field constraints or discriminators. Enum values
and mapping keys must survive a JSON round trip without collisions or type
drift. Validators may normalize values, but the returned runtime shape must
still match the annotation. Dynamic Any values are limited to string-keyed
JSON-shaped trees and recognized secret leaves.
MINT rejects RootModel, TypedDict, dataclasses, Pydantic dataclasses,
NamedTuple, extra="allow", arbitrary types, unsafe mapping keys, and
serialization-changing features. The latter include field/model serializers,
json_encoders, GetPydanticSchema, SerializeAsAny, plain/wrap serializers,
and field exclude/exclude_if. Broad Pydantic helper types such as Json
and ImportString are not config scalars; use a supported storage type plus a
validator. See the
1.1 migration guide
for the complete migration list.
The platform owns allowed_experiment_types; a decorator config model and a
plugin-supplied owned_keys scope cannot claim it. The value must be
list[str] | None, and corrupt persisted policy denies every experiment type
until repaired. Typed and platform-owned fields are updated separately.
The user-facing plugin-config GET returns platform-owned public fields plus a
sanitized cached typed snapshot only when the loaded provider, plugin name,
resolver, and exact decorator model all match. It does not fall back to raw
stored values for unloaded, untyped, external, or Docker-hosted plugins. The
plugin-scoped internal transport can still read raw backing values for an
isolated runtime, and every write through that transport must supply explicit
owned_keys without platform-owned fields.
Full saves and shallow patches prepare a secret-free backing value together with its exact effective snapshot. Environment/startup overrides are included in preflight and the live commit but are not materialized into storage. A patch replaces only its explicit top-level keys and uses bounded compare-and-swap retries. Preflight and host persistence callbacks may be synchronous or return one awaitable; nested awaitables fail the transaction instead of leaving work running after an uncommitted result.
Each browser tab gets an ephemeral session. Jobs are independent and may run
concurrently across a user's tabs. Process and staged jobs snapshot input and
effective config at submission time. Service jobs snapshot input and expose
the submitted config as JobContext.config, but run against the initialized
host object: self.settings, plugin attributes, and services are live when the
handler executes. Sessions and jobs are not persisted and do not survive a
daemon restart.
@job works for both custom Vue and @generated_ui plugins. Generated
contracts expose typed usePluginJobs() overloads for definition inputs, job
handles, and outputs, plus ready, refresh, jobsFor, deleteJob,
clearFinishedJobs, pause, resume, and idempotent dispose lifecycle
helpers. Use ordinary Path parameters for file or directory inputs; HTTP
submissions stage opaque owner/session-scoped uploads into per-job writable
workspaces, while direct Python calls keep the caller's local paths.
PluginTestHarness runs the real scheduled path without test-owned sessions,
actors, polling, or temporary-file plumbing.
For large outputs, write beneath JobContext.output_path() and return
ManagedFileResult. The host adopts and hashes the file without base64 or a
worker-IPC copy. It either exposes an expiring authenticated download or runs a
trusted @job_finalizer with ManagedFile, the original actor, immutable
input/config snapshots, and a stable retry idempotency key. The finalizer's
return annotation becomes the typed value inside the generated wire-result
envelope. Finalization is async and cannot be cancelled by clients. Each
attempt has a configured timeout; blocking libraries must run through
JobFinalizationContext.run_blocking() so timeout cancellation drains the
thread before the actor scope, managed file, quota, or live plugin services are
released. A thread that never returns can therefore extend wall-clock teardown
beyond that timeout. Platform mutation APIs remain responsible for current
authorization. See the
1.1 migration guide.
The shared scheduler enforces CPU slots globally and a concurrent-job limit per
user across all of that user's sessions. Each synchronous job runs in a fresh
worker process. Configure the two primary limits with MINT_JOB_CPU_SLOTS and
MINT_JOB_MAX_CONCURRENT_PER_USER, or the matching mint daemon flags.
The in-memory runtime also bounds queued work, retained terminal jobs,
serialized payloads, and result lifetime. Configure these limits with
MINT_JOB_MAX_QUEUED, MINT_JOB_MAX_QUEUED_PER_USER,
MINT_JOB_MAX_SESSIONS, MINT_JOB_MAX_SESSIONS_PER_USER,
MINT_JOB_MAX_RETAINED, MINT_JOB_MAX_RETAINED_PER_USER,
MINT_JOB_MAX_INPUT_BYTES, MINT_JOB_MAX_TEMP_BYTES_TOTAL,
MINT_JOB_MAX_TEMP_BYTES_PER_USER, MINT_JOB_MAX_RESULT_BYTES,
MINT_JOB_MAX_MANAGED_RESULT_BYTES,
MINT_JOB_MAX_RESULT_BYTES_TOTAL, MINT_JOB_MAX_RESULT_BYTES_PER_USER, and
MINT_JOB_RESULT_TTL_HOURS.
The temporary-byte limits are shared service-wide across plugins, ordinary path storage, staged parts, and multipart request spools. During upload adoption, both the request spool and retained workspace copy may count.
Process workers reconstruct the zero-argument plugin class and inject the
submission-time settings snapshot. Jobs should depend on typed input,
self.settings, and JobContext.
The trusted worker snapshot contains effective secret values, but job state,
events, and the public settings API never expose them.
For Docker, Linux, and WSL, run one foreground host process:
mint daemon \
--platform-dir . \
--app mint_sdk.runtime:create_plugin_app \
--port 8000 \
--cpu-slots 4 \
--max-concurrent-per-user 2
Forwarded headers are trusted only from loopback by default. When a reverse
proxy runs on another address, pass its explicit IP or CIDR with
--forwarded-allow-ips; do not use a wildcard on an exposed deployment.
The previous mint platform daemon ... service-management commands remain
available for compatibility.
Monorepo plugins must declare non-standard project paths so every CLI command uses the same frontend and generated client:
[tool.mint]
frontend_dir = "packages/ui"
generated_dir = "packages/contracts/client"
Both values are project-relative. generated_dir defaults to
<frontend_dir>/src/generated when omitted. Conventional single-package
plugins need no path settings.
mint doctor can also inspect split Python packages and several plugin
projects from one repository root:
[tool.mint]
python_source_dirs = ["packages/leaf"]
[tool.mint.workspace]
plugin_members = ["plugins/*"]
[[tool.mint.doctor.import_rules]]
source = "leaf.analyzer"
forbid = ["mint_sdk", "fastapi", "leaf.api"]
Workspace members are explicit globs. Directories without a
mint.plugins entry point are reported as skipped, and each plugin receives a
separate text/JSON result group. Run mint doctor --strict when warnings must
also fail CI. Import rules are static checks of normal Python imports; they do
not import project code or guess dynamic imports.
For R-backed analyses:
mint init drp-r --mode standard
mint add r-analysis drp-fit --page
mint doctor --r --explain
mint sdk generate
This creates an RAnalysisBridge service, FastAPI route, typed frontend composable, optional starter page, and a small mint_bridge.R helper for reading inputs, writing outputs, accessing the current experiment id, and writing analysis artifacts while keeping Python/Pydantic as the frontend contract source of truth.
For standard biology design data:
mint add data-template --list --json
mint docs template plate-map
mint add data-template plate-map --page
Built-in templates include plate-map, sample-sheet, sample-prep, dose-response, calibration-curve, time-course, protocol-steps, assay-matrix, reagent-list, flow-cytometry-panel, instrument-run, and qpcr-plate. Generated template routes expose schema/default endpoints and merge multiple templates under design_data.templates, so a plugin can combine plate layouts, sample metadata, sample prep, reagents, protocols, calibration curves, time courses, readout matrices, cytometry panels, instrument run queues, and qPCR plates without clobbering prior template data.
Use create_template_collection() / save_template_collection() when a backend route needs to persist a coordinated set of templates, and load_template_collection() when a route needs all envelopes stored for an experiment. Single-template save_template() / load_template() remains available for narrow routes.
mint add data-template-pack <name> --page generates those collection routes and the matching frontend composable for curated packs, so plugin authors can save a whole experiment design scaffold with one API call.
For experiment object files, let the platform choose local, S3, or OpenStack
Swift storage and keep only the returned reference in your experiment data.
Standalone plugin runs use a local store under
~/.mint/plugins/<plugin-name>/objects.
class MyPlugin(AnalysisPlugin):
async def initialize(self, context=None):
self._context = context
async def save_report(self, experiment_id: int, payload: bytes) -> dict:
store = self.get_data_store(experiment_id)
ref = await store.put_bytes(
"reports/report.json",
payload,
content_type="application/json",
metadata={"kind": "qc-report"},
)
return ref.to_dict()
The platform stores object bytes under {server.dataPath}/objects by default.
Admins can switch the object backend between storage.objects.backend = "local"
"s3", and "swift" and can set storage.objects.localPath for local storage.
When the backend is S3, the same SDK calls write to storage.s3.objectBucket
under storage.s3.objectPrefix. When the backend is Swift, they write to
storage.swift.objectContainer under storage.swift.objectPrefix. The platform
owns endpoint, region, access key, secret key, session token, SSL, path-style,
Keystone auth URL, project/domain, and Swift password settings; saved
credentials are encrypted at rest and redacted from admin config responses. Use
Admin -> Configuration -> Object Storage to choose Local Path, S3 Bucket, or
OpenStack Swift, and the Test Connection button to validate provider access.
Isolated plugin uploads use multipart transfer for put_file / put_fileobj
instead of base64 JSON.
S3-compatible provider settings may also come from environment variables. The
platform reads endpoint/region names such as MINT_S3_ENDPOINT_URL,
MINT_S3_REGION_NAME, S3_ENDPOINT_URL, and AWS_ENDPOINT_URL_S3; credential
names such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
AWS_SESSION_TOKEN; and MINT-specific MINT_S3_* /
MINT_STORAGE__S3__* variants for SSL and path-style addressing.
Native Swift settings may come from MINT_SWIFT_* /
MINT_STORAGE__SWIFT__* variables or standard OpenStack variables such as
OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_PROJECT_NAME,
OS_USER_DOMAIN_NAME, OS_PROJECT_DOMAIN_NAME, OS_AUTH_VERSION, and
OS_REGION_NAME.
Scripts can use MINTClient.objects for the same storage API by passing
plugin_id explicitly. get_s3_connector() and platform S3 credential access
were removed in 1.1. Integrated plugins call get_data_store(experiment_id).
A standalone script may construct S3Connector with an explicit
S3ConnectionConfig or client.
At the Python layer, plugins implement the AnalysisPlugin interface:
from mint_sdk import AnalysisPlugin, PluginCapabilities, mint_plugin
from fastapi import APIRouter
router = APIRouter()
@router.get("/hello")
async def hello():
return {"message": "Hello from my plugin!"}
@mint_plugin(
analysis_type="metabolomics",
routes_prefix="/my-plugin",
capabilities=PluginCapabilities(
requires_auth=True,
requires_experiments=True,
),
)
class MyPlugin(AnalysisPlugin):
def get_routers(self):
return [(router, "")]
async def initialize(self, context=None):
self._context = context
async def shutdown(self):
pass
Plugin Package Structure
mint-plugin-example/
├── pyproject.toml
├── README.md
└── src/mint_plugin_example/
├── __init__.py
└── plugin.py
pyproject.toml
[project]
name = "mint-plugin-example"
version = "0.1.0"
dependencies = ["mint-sdk>=1.1.0"]
[project.entry-points."mint.plugins"]
example = "mint_plugin_example.plugin:MyPlugin"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/mint_plugin_example"]
The entry point mint.plugins is how the platform discovers your plugin.
Platform Context
When running integrated with the platform, your plugin receives a PlatformContext that provides access to:
- Authentication dependencies (
get_current_user_dependency()) - Capability-scoped repositories and shared plugin database sessions
- Persisted configuration for the current plugin
- Platform-managed experiment object storage
- Typed notification and calendar publication
The context does not expose full platform settings or platform secrets. Use
self.settings for the plugin's typed configuration and
get_data_store(experiment_id) for experiment files.
async def initialize(self, context=None):
self._context = context
if context:
# Running integrated - use platform services
self.experiment_repo = context.get_experiment_repository()
else:
# Running standalone
pass
Installation Commands
These commands are for a developer environment or the trusted in-process plugin path. In-process installation retains normal package, Git, editable/local-source, wheel, and source-distribution support.
# Install from GitHub
uv add git+https://github.com/org/mint-plugin-example
# Install specific version
uv add git+https://github.com/org/mint-plugin-example@v1.0.0
# Install from PyPI
uv add mint-plugin-example
# Install local plugin for development
uv add --editable ./my-plugin
Subprocess installation
A subprocess plugin source must be an administrator-trusted, existing local
.whl. MINT rejects package requirements, Git URLs, editable installs,
source trees, source archives, and source distributions for this path.
Dependencies must have compatible binary wheels; source builds are disabled.
Keep the selected wheel at its persisted path. If it is missing at startup, MINT leaves the plugin disabled and does not use a registry, network download, or source fallback. This release does not include a trusted wheel builder or a content-hash artifact cache.
The virtual environment and separate process isolate dependencies and ordinary failures, not hostile code. They are not an OS sandbox. Deploy untrusted code through an external HTTP or Docker-managed runtime with explicit operating system or container controls. Snapshot rollback can remove packages added after the snapshot, but MINT refuses to restore missing or changed packages without immutable trusted wheels.
For an external HTTP runtime, the deployment operator sets
MINT_EXTERNAL_PLUGIN_<NORMALIZED_NAME>_TOKEN on MINT and passes the same
secret to the external service as MINT_PLUGIN_TOKEN. The normalized name is
uppercase with runs of non-alphanumeric characters replaced by _. Use at
least 32 non-whitespace characters. MINT reads the value at startup and does
not register the runtime when it is missing or weak. It does not store, expose,
or rotate this secret. Normalized token keys must be unique; all colliding
runtimes remain disabled. Targets must be absolute http or https URLs with
a hostname and valid optional port, without userinfo, query strings, or
fragments. Plain HTTP is accepted only for localhost, 127.0.0.0/8, or
::1; remote targets require HTTPS. Tokens used by an instrument to
authenticate to a plugin-owned API are separate credentials owned entirely by
that plugin.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mint_sdk-1.1.0b1.tar.gz.
File metadata
- Download URL: mint_sdk-1.1.0b1.tar.gz
- Upload date:
- Size: 2.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03ce6044a525a80ec67da428264584fe0ffa0e63f349b36180aaa6b4832c212e
|
|
| MD5 |
f89a72498489e5e8f048a945d84891d3
|
|
| BLAKE2b-256 |
fb6e47fcbfc66c9f9afcee15e77120395da27bcde3bc8114a4c9e8581b3e7d61
|
File details
Details for the file mint_sdk-1.1.0b1-py3-none-any.whl.
File metadata
- Download URL: mint_sdk-1.1.0b1-py3-none-any.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90ac45a937d26175ac8f528af05448e7600741e6795d20a9d751d4937029d651
|
|
| MD5 |
c2bd1afff67e825690d718e42be954d1
|
|
| BLAKE2b-256 |
bf84c2fa08b2584112ef9627dcafdf9ce1022bd03f366977a38542e211b74289
|