Reusable async Python client for the Matelab Integration Contract
Project description
matelab-python-sdk
Reusable async Python client for the Matelab Integration Contract.
The current alpha is 0.1.0a1. [project].version in pyproject.toml is the sole SDK version source;
uv.lock only mirrors that source.
The SDK is pinned to the immutable matelab-spec v0.1.0 Contract Release. The sole release pin is
contracts/matelab-integration-v1.lock.json, which records
the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
Installation
Python 3.11 or newer is required. Install the alpha from a package index with either:
uv add matelab-python-sdk
python -m pip install matelab-python-sdk
Development installs use the locked checkout:
uv sync --frozen
To test the same artifact a downstream Consumer will install, build and install the wheel:
uv build --no-build-isolation --out-dir dist/release
python -m pip install dist/release/matelab_python_sdk-0.1.0a1-py3-none-any.whl
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract tag, commit, and checksum recorded below.
Design
The public module is intentionally small:
from matelab import AsyncMatelab
async with AsyncMatelab() as client:
session = await client.authenticate("user@example.org", "password")
assert client.session is session
notebooks = await client.notebooks.list()
notebook = notebooks.owned[0].ref
records = await client.records.list(notebook=notebook)
record = await client.records.read(notebook=notebook, record=records.records[0].ref)
AsyncMatelab() uses https://matelab.iphy.ac.cn/api by default. Pass another Provider API root
explicitly when needed, for example AsyncMatelab("https://custom.example/api").
Session ownership
Each AsyncMatelab instance owns at most one current, process-local Session. The SDK injects its bearer
token, refreshes it under a per-instance async lock, performs bounded safe retries, and exposes every token
rotation through client.session. If refresh succeeds but the subsequent business request fails,
client.session still contains the refreshed token pair.
| SDK responsibility | Integrator responsibility |
|---|---|
| Bearer injection, expiry checks, refresh and bounded retry | Redis/database/file persistence and encryption |
| Per-instance, in-process refresh serialization | Cross-process locking and conflict handling |
| Contract validation of token and identity responses | Mapping userid/session_id to a persisted Session |
Latest immutable Session through client.session |
Revocation, cleanup, and saving after each call |
Session, Token, and Identity are immutable value objects, and token values are excluded from their
representations. The SDK does not read tokens from environment variables and does not provide a session store.
Credential authentication installs the returned Session on the client:
async with AsyncMatelab() as client:
session = await client.authenticate(username, password)
assert client.session is session
External token exchange validates the original access-token identity, refreshes the supplied refresh token, validates the refreshed access-token identity, and rejects a userid mismatch. The existing client Session is replaced only after all three steps succeed:
async with AsyncMatelab() as client:
session = await client.bind_external_tokens(access_token, refresh_token)
assert client.session is session
Restore a previously validated Session by passing it to the constructor. Construction performs no network request:
persisted_session = await session_store.load(userid, session_id)
async with AsyncMatelab(session=persisted_session) as client:
result = await handle_request(client)
A Web or MCP integration should create one client for one logical session, then save the latest Session in
finally, including when a business call fails after refresh:
persisted_session = await session_store.load(userid, session_id)
client = AsyncMatelab(session=persisted_session, http_client=shared_http_client)
try:
result = await handle_request(client)
finally:
latest_session = client.session
try:
if latest_session is not None:
await session_store.save(userid, session_id, latest_session)
finally:
await client.aclose()
If several processes can use the same persisted session, the integration must place its own distributed lock
around load, use, and save. The SDK lock only coordinates refreshes inside one AsyncMatelab instance.
Different logical sessions require different clients. They may reuse the same externally managed HTTP connection
pool, but must never share one global AsyncMatelab singleton:
alice_client = AsyncMatelab(session=alice_session, http_client=shared_http_client)
bob_client = AsyncMatelab(session=bob_session, http_client=shared_http_client)
src/matelab/_generated is a private wire layer. Applications should not depend on its file
layout or generated class names. The distribution includes py.typed, so type checkers can consume the
public annotations directly from an installed wheel.
The current public domain scope includes authentication, group/user discovery, template and notebook lifecycle operations, record discovery/lifecycle operations, comment reads, and streaming record or comment attachment downloads, resumable file staging, literature discovery/lifecycle workflows, and personal cloud-drive management.
Implementation roadmap
docs/roadmap.md is the complete SDK-only execution plan. It assigns all 71
matelab-spec v0.1.0 operations to ordered work packages, defines the machine-readable coverage that
must be added, records Provider-risk gates, and specifies the final completion checks.
The SDK exposes all 71 Contract operations through public domain interfaces. It deliberately excludes MCP migration, adjacent-repository changes, external publishing, and automatic mutation against a real Provider.
Machine-readable status lives in
docs/operation-coverage.yaml. An exact-coverage test keeps its 71
operation IDs, methods, paths, work packages, and Provider issue references aligned with the pinned
OpenAPI snapshot.
| Domain | Implemented | Planned | Current public surface |
|---|---|---|---|
| Authentication | 4 | 0 | authenticate, bind_external_tokens, refresh, resolve_identity, exchange_chat_sso_code |
| Groups and users | 2 | 0 | groups.list, users.search |
| Notebooks | 6 | 0 | notebooks.list/create/update/shares/share/update_share/unshare |
| Records | 18 | 0 | Discovery, reads, lifecycle, typed patch/attachments, relations, and downloads |
| Comments | 5 | 0 | Read, staged attachment upload, create/update/delete, and download |
| Templates | 13 | 0 | Discovery, content, lifecycle, sharing, groups, and marketplace |
| File staging | 1 | 0 | Resumable fragment staging and compensating abort request |
| Literature | 13 | 0 | Libraries, items, canonical metadata, comments, sharing, PDF lifecycle and streaming |
| Cloud drive | 9 | 0 | Personal root/folders/files, staged binding, metadata, move/delete and streaming |
| Total | 71 | 0 | No operation is intentionally unexposed |
Stability and known capability limits
Coverage currently contains 15 stable and 56 experimental operations. The stable operation IDs are
resolveCurrentIdentity, loginTokenSet, refreshTokenSet, exchangeChatSsoCode,
shareMultipleTemplatesWithUsers, removeTemplateFromGroup, deleteNotebookShare, listNotebooks,
listNotebookRecords, exportRecords, deleteRecordsByUid, copyRecord, readRecord,
deletePersonalLiteratureItem, and readLiteratureCreateTemplate.
Every other implemented operation is explicitly experimental; the exact per-operation list and its
PVD/PCG references live in
docs/operation-coverage.yaml. There are no intentionally_unexposed
operations and no planned operations. Experimental support means the SDK validates and exposes the
pinned Contract while preserving limitations such as unstable ordering/pagination, incomplete mutation
acknowledgements, missing batch atomicity or idempotency, weak attachment ownership binding, and known
Provider authorization gaps. It does not turn those limitations into SDK guarantees.
Chat iframe SSO consumes a one-time code and shared key. Both arguments are treated as secrets, the request is never
automatically retried, and the returned token set is stored in the same in-memory Session shape as credential login:
session = await client.exchange_chat_sso_code(code="chat-sanitizedcode123", key="sanitized-shared-key")
Group and user discovery expose sharing identities without inventing Provider pagination:
from matelab import UserSearchScope
groups = await client.groups.list()
targets = await client.users.search("Example Researcher", scope=UserSearchScope.SAME_INSTITUTE)
Both results explicitly report provider_unspecified ordering. Group members belong only to
groups.members_for, not to every returned group. These two discovery interfaces are experimental because the
Provider returns members for an unstable first group and user search is unpaged, unordered, and not field-minimized
(PVD-006, PVD-029, PCG-011).
Notebook create/update and direct sharing keep write acknowledgement separate from what a readback can prove:
from matelab import NotebookMetadata, NotebookSharePermissionGrant
created = await client.notebooks.create(NotebookMetadata(title="Example Notebook"))
shares = await client.notebooks.share(notebook, [target.ref])
updated = await client.notebooks.update_share(
shares.visible_after_write[0].ref, NotebookSharePermissionGrant(write=True, create=True)
)
Create cannot return a notebook ref because the Provider returns no identity. Owned updates are read back by database ID and report matching, mismatched, or missing observations, containing PVD-003 false success without claiming an atomic guarantee. Share results similarly report post-write visibility rather than invented affected rows. A stored share mask of zero still has effective read access (PVD-010), and share-list order remains unspecified.
Template discovery keeps a template database identity separate from direct-share, market-acquisition, and group relation identities:
templates = await client.templates.list()
market = await client.templates.search_market("calibration", page=1, page_size=20)
content = await client.templates.read(templates.owned[0].ref)
The market result reports the Provider total_count, the requested and effective page sizes, and a has_more
value explicitly marked as derived_from_total; it does not claim a stable order or continuation token. Canonical
modules are mapped to public TemplateModule values and retain additive module attributes. Template reads remain
experimental because Provider discovery ordering/pagination and historical images compatibility are not fully
stable (PCG-003, PCG-009, PVD-013, PVD-022).
Template writes remain separate operations: metadata, canonical modules, and usage HTML are not presented as one
transaction. Metadata create returns the Provider template ID; copy explicitly returns no new identity because the
Provider supplies none. Direct-share, market-acquisition, and group relation refs are distinct and are required by
their matching removal methods. Mutation results report readback observations without inventing affected rows.
Metadata and intro results explicitly report that they do not advance the marketplace revision (PVD-021).
UploadBindingRef.new() creates the fresh hidden correlation value required by intro attachment binding, while the
result still records that the Provider does not verify the uploader (PVD-026).
Extended record reads stay behind the same records interface:
from matelab import RecordFieldExtraction, RecordLocator
exported = await client.records.export([RecordLocator(notebook=notebook, record=record)])
matches = await client.records.search(
notebooks=[notebook], extractions=[RecordFieldExtraction(alias="notes", path=("Notes",))]
)
page = await client.records.page(notebook)
deleted = await client.records.recycle_bin(notebook)
relations = await client.records.relations(notebook=notebook, record=record)
records.page fixes the legacy request to page_size=0&default=1, preventing the known owner-preference writes
described by PVD-039; its total is derived from the Provider's complete matching ID list. Public catalog records and
deleted records use identities distinct from active RecordRef. Relation targets separately expose declared and
resolved notebook IDs because the Provider may return dangling or incomplete identities. Search and relation order
remain unspecified, and no continuation token is invented.
Record creation keeps blank creation and structured import as separate capabilities:
from matelab import RecordImportItem, RecordImportTemplate, TemplateRef
blank = await client.records.create_blank(notebook=notebook, title="Blank Record", record_uid="caller-generated-uid")
imported = await client.records.import_dataset(
notebook=notebook,
template=RecordImportTemplate(template=TemplateRef(template_id=8), title="Example Template"),
items=[RecordImportItem(record_uid="import-uid", title="Imported", data={"Notes": "value"})],
)
A caller-supplied blank-record UID is read back; when the Provider generates it, the result explicitly has no invented identity. Import validates the complete batch with generated wire models but cannot map returned database IDs to individual inputs or promise atomicity (PCG-008). Delete means moving records into the recycle bin, not permanent deletion. Delete and restore results classify only post-write observations; restore never treats an active row with the same UID but a different database ID as proof of success (PCG-005). Record mutations are not automatically retried.
Record patching exposes a deliberately narrower capability than the raw Provider operation. Scalar/module changes
cannot smuggle Provider-native attachment strings; staged attachments use separate form-removal, table-replacement,
files append/replace/remove, and rich-text types. Unsafe form replacement and table-file removal are absent, while a
files/images removal is rejected when the observed module contains the same hash more than once (PVD-014 through
PVD-016). Record.content_sha256 can be supplied as a client-side precondition, but results explicitly label it as
advisory read-before-write rather than Provider CAS. Database, active-browser, and unclassified acknowledgements
remain distinct, and mutation retries stay disabled.
Relation addition reads both endpoints and checks their resolved data server before writing; this reduces PVD-019 risk but is not an atomic Provider authorization guarantee. Relation deletion refuses an observed cross-notebook target-ID collision because the Provider ignores target notebook identity (PVD-020), then reports readback and any other relation that disappeared without inventing an affected-row count.
Comment upload follows the Provider's literal one-request upload field, not the incompatible Front fragment
protocol (PVD-037). Comment creation derives an ID only from a unique post-write observation. Edit and delete require
a currently observed caller-owned comment and read it back, containing the Provider's edit false-success behavior
(PVD-004). Staged comment attachments have no Contract abort operation, and binding remains affected by PVD-026.
Attachment bytes are streamed and must be consumed or closed explicitly:
from matelab import ByteRange
comments = await client.records.comments(notebook=notebook, record=record)
attachment = comments.comments[0].attachments[0]
async with await client.records.download_comment_attachment(attachment, byte_range=ByteRange.from_start(0)) as download:
async for chunk in download:
consume(chunk)
DownloadStream exposes status, content type, length, range, and disposition metadata without buffering the
complete file. Streams are not automatically replayed. ByteRange deliberately rejects bytes=0-0 (PVD-002).
Comment attachment refs preserve the notebook/record/comment context where they were observed, but they are not
Provider authorization credentials: current Providers do not verify that association (PVD-038).
Cross-domain staging keeps resumable state and completed-file identity separate:
import hashlib
from matelab import StagedFile
pdf_bytes = b"sanitized PDF bytes"
staged = await client.uploads.stage(
pdf_bytes,
filename="example.pdf",
fragment_size=len(pdf_bytes),
complete_sha256=hashlib.sha256(pdf_bytes).hexdigest(),
)
assert isinstance(staged, StagedFile)
For multiple fragments, pass StagedFileFragment.session into the next call. next_offset is explicitly a
caller-side total derived from declared fragment sizes; the Provider does not confirm an offset. A final result
contains the Provider hash, size, temporary row identity and fresh hidden binding value, but does not claim that a
later literature/cloud operation checks the uploader or consumes the file exactly once. uploads.abort exposes the
Provider's legacy code-2 cancellation signal as compensating cleanup that is not independently verified (PVD-028).
Staging mutations are never automatically retried.
Literature identities distinguish the personal library, shared libraries and pending incoming copies:
from matelab import DoiMetadataSource, LiteratureMetadata
libraries = await client.literature.libraries()
page = await client.literature.list(libraries.personal.ref)
detail = await client.literature.read(page.items[0].ref)
schema = await client.literature.creation_schema()
if schema.metadata_extraction_available:
candidates = await client.literature.extract_metadata(DoiMetadataSource("10.0000/example"))
created = await client.literature.create(
LiteratureMetadata(title="Example import", doi="10.0000/example"), staged_pdf=staged
)
assert created.created_item is None
Create never guesses the new item from list position because the Provider returns no ID. Canonical update reads the
item first and refuses to drop source/hidden fields unless allow_source_metadata_loss=True is explicit (PVD-027).
PDF replace/delete are separate, read-back-verified mutations and are not presented as atomic with metadata
(PCG-010). Permanent personal deletion is named permanently_delete, reports snapshot verification, and is marked
non-recoverable. Sharing requires list-observed item summaries, user-search summaries and a resolved caller identity;
its result deliberately contains no invented per-recipient IDs or batch atomicity (PVD-012, PVD-036).
Literature comments use one public save intent: detail is read first, an existing caller-owned comment is edited, and
otherwise a comment is created. Multiple caller-owned comments are rejected as ambiguous (PVD-035). A staged
attachment can replace one matelab-staged-file marker; raw temporary URLs are rejected. These checks contain common
misuse but do not repair the Provider's cross-user UID lookup (PVD-026). Shared-library reads and writes remain
experimental because the Provider permission JOIN is not scoped to the current user (PVD-011); successful SDK calls
must not be treated as independent authorization proof. Literature PDF downloads reuse DownloadStream and the
stable ByteRange subset.
The personal cloud-drive surface keeps root, folder, final file and temporary staging identities separate:
from matelab import CloudFolderMetadata
listing = await client.cloud_drive.list()
folder_result = await client.cloud_drive.create_folder(CloudFolderMetadata(name="Example data"))
bound = await client.cloud_drive.bind_staged_file(staged, target=folder_result.folder)
if bound.observed is not None:
renamed = await client.cloud_drive.update_file(
bound.observed, filename="example-renamed.pdf", description="Sanitized description"
)
CloudDriveListing contains a typed file page, complete folder tree, quota usage and personal-root permissions rather
than flattening them into one ambiguous collection. Folder browse results retain their location; filename searches
are explicitly root-wide and return location=None because the Provider omits each match's folder ID. Ordering has
no stable ID tie-breaker (PCG-003, PVD-013).
Folder create returns the Provider ID and all folder mutations read back the complete tree. Staged finalize accepts a
completed StagedFile, then paginates the target folder and returns a final CloudFileRef only for one exact
filename/hash/size observation. This is useful evidence, not an uploader-ownership guarantee: the Provider binds by
temporary row ID without checking its owner (PVD-031), and finalize atomicity/idempotency remain absent (PCG-012).
Batch move and permanent delete keep Provider item results as None, report only post-write observations and do not
claim atomicity. Permanent deletion is named permanently_delete_files and marked non-recoverable. Cloud downloads
resolve bytes from the final file identity and reuse DownloadStream, thumbnail/preview choices and the PVD-002-safe
range subset. Cloud mutations are not automatically retried.
To run the implementation as a persistent Codex goal, start a task in this repository and use:
完整阅读并严格遵循
AGENTS.md、README.md和docs/roadmap.md。创建并持续执行一个 goal: 只修改当前仓库,按照 roadmap 从第一个未完成 work package 开始,完成 71-operation 精确覆盖和全部 SDK 领域 interface;每个 package 通过局部验证后自动继续,最终让 generation--check、Ruff、 Ruff format、Basedpyright、Pytest 和 package build 全部通过。不要修改相邻仓库,不执行生产 Provider mutation,不 commit、push、tag 或发布。
Owned/shared NotebookRef, public PublicNotebookRef, RecordRef, and RecordVersionRef keep
Provider identifiers distinct. Historical reads first re-read the authorized current record and confirm
that the requested version is still present in its modify_log; both reads write Provider audit entries.
Errors are separated into Provider business errors, authentication errors, HTTP/transport errors, Integration Contract response errors, and client-side usage errors. Token values and sensitive response fields are redacted from error text and retained diagnostic payloads.
Development
uv sync
uv run python scripts/generate_models.py
uv run python scripts/generate_models.py --check
uv run ruff check .
uv run ruff format --check .
uv run basedpyright
uv run pytest
uv build
The generator first verifies the contract lock, OpenAPI release metadata, and snapshot digest. It then
creates a temporary OpenAPI 3.1 generation projection, resolves references without network access, and
generates private component, operation-response, and parameter models. The projection only flattens pure
object inheritance that the generator cannot otherwise preserve correctly; the checked-in release
snapshot remains unchanged. --check performs the same validation and deterministic generation without
writing the checked-in models. The current lock resolves datamodel-code-generator 0.71.0 and
hatchling 1.31.0. Published metadata requires httpx>=0.27.2,<1 and
pydantic>=2.12.5,<3; the build backend requires hatchling>=1.27,<2. These lower bounds are verified
against the complete test suite on the supported Python boundary versions rather than inferred from
uv.lock. The exact toolchain remains locked for development and release builds. Basedpyright and its
Node wheel retain the compatible exact pair basedpyright==1.39.9 and
nodejs-wheel-binaries==22.20.0.
Opt-in Provider consumer smoke
tests/provider/test_provider_smoke.py exercises the consumer flow through only the public SDK interface:
credential authentication, extraction of the returned token pair, exchange through a new
AsyncMatelab.bind_external_tokens instance, userid consistency checks, owned/shared notebook discovery,
record listing, and current-record reading. It is not Provider Verification and is skipped by default.
Prefer an explicitly isolated Provider. A production target requires explicit authorization for the current run
and a dedicated test fixture. Authentication and external-token refresh persist Provider token state, and
GET /eln_items/item_view writes a Provider audit entry even though it is a read operation. The smoke therefore
requires explicit target and write acknowledgements:
MATELAB_PROVIDER_SMOKE=1
MATELAB_PROVIDER_SMOKE_ISOLATED=1
MATELAB_PROVIDER_SMOKE_ALLOW_WRITES=1
For an explicitly authorized production test fixture, leave MATELAB_PROVIDER_SMOKE_ISOLATED empty and use:
MATELAB_PROVIDER_SMOKE_PRODUCTION_ACKNOWLEDGED=1
Fill the git-ignored local .env.test with exactly one target acknowledgement and the fixture:
MATELAB_PROVIDER_SMOKE_BASE_URLMATELAB_PROVIDER_SMOKE_USERNAMEMATELAB_PROVIDER_SMOKE_PASSWORDMATELAB_PROVIDER_SMOKE_EXPECTED_USERIDMATELAB_PROVIDER_SMOKE_NOTEBOOK_IDMATELAB_PROVIDER_SMOKE_RECORD_DATABASE_IDMATELAB_PROVIDER_SMOKE_RECORD_UID
The userid and both record identifiers are checked before the record read so an ambiguous or incorrect fixture fails safely. With the environment prepared:
uv run --env-file .env.test pytest -m provider tests/provider/test_provider_smoke.py
The file is never loaded implicitly, so the normal test suite remains safely skipped. Never use production without explicit authorization for that run, and never commit its credentials.
Reproducible release build
Build from a clean release commit (or its tag) and set the archive timestamp to that commit's
committer timestamp. pyproject.toml declares the supported Hatchling range, while uv.lock supplies the
exact version used by the frozen, no-build-isolation release environment:
export SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)"
uv sync --frozen
uv run python scripts/generate_models.py --check
uv build --no-build-isolation --out-dir dist/release
uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
(cd dist/release && sha256sum *.whl *.tar.gz > SHA256SUMS)
Rebuilding the same commit with the same locked environment and SOURCE_DATE_EPOCH must produce
byte-identical wheel and source distribution hashes. The release is bound to matelab-spec v0.1.0,
commit 236795db1aa7a784ad9df936e9547e8d529be53f, and OpenAPI SHA-256
62ca07dcd7a0b3f80eb381a0d1447316f69faa751bafb8ab7c6a751e619d8164.
Project details
Release history Release notifications | RSS feed
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 matelab_python_sdk-0.1.0a1.tar.gz.
File metadata
- Download URL: matelab_python_sdk-0.1.0a1.tar.gz
- Upload date:
- Size: 231.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f11f13d460aa70102d9cd1d99f534b6b799ed67f3613af1fd52f33e83b46340
|
|
| MD5 |
dc7edb879825544d48140a80772f6c02
|
|
| BLAKE2b-256 |
1bf45466f1715f8ab09ad3f71045041a4b1ad3104cc0a50db5b807a0b5216572
|
Provenance
The following attestation bundles were made for matelab_python_sdk-0.1.0a1.tar.gz:
Publisher:
release.yml on tiannianzhu/matelab-python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
matelab_python_sdk-0.1.0a1.tar.gz -
Subject digest:
5f11f13d460aa70102d9cd1d99f534b6b799ed67f3613af1fd52f33e83b46340 - Sigstore transparency entry: 2256598639
- Sigstore integration time:
-
Permalink:
tiannianzhu/matelab-python-sdk@f47dd2836af395136b0cea3a9a1dc5a761d61b82 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/tiannianzhu
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f47dd2836af395136b0cea3a9a1dc5a761d61b82 -
Trigger Event:
push
-
Statement type:
File details
Details for the file matelab_python_sdk-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: matelab_python_sdk-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 90.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10cad18f90a1711547a797fd786bcef2dfb026d521bfb32d44608b23fdcdafb2
|
|
| MD5 |
534bfa43483a4d7909c5da47e6cfc3d7
|
|
| BLAKE2b-256 |
49e837ee429286a82c2682187132e6a7b24522e8c46d9854eb0053ca59028d04
|
Provenance
The following attestation bundles were made for matelab_python_sdk-0.1.0a1-py3-none-any.whl:
Publisher:
release.yml on tiannianzhu/matelab-python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
matelab_python_sdk-0.1.0a1-py3-none-any.whl -
Subject digest:
10cad18f90a1711547a797fd786bcef2dfb026d521bfb32d44608b23fdcdafb2 - Sigstore transparency entry: 2256598650
- Sigstore integration time:
-
Permalink:
tiannianzhu/matelab-python-sdk@f47dd2836af395136b0cea3a9a1dc5a761d61b82 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/tiannianzhu
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f47dd2836af395136b0cea3a9a1dc5a761d61b82 -
Trigger Event:
push
-
Statement type: