Sanshain Conan Plugin
A Conan extension to manage API specifications (OpenAPI, AsyncAPI, gRPC/Proto) during the C++ build process.
Compatibility: sanshain-conan 2.x speaks Sanshain Service 2.x.
Description
Sanshain Conan Plugin allows you to:
- Provide: Upload your service's API specification to the Sanshain service under the version declared in the spec file itself.
- Require: Download API snippets of other services, pinned to an exact version, to generate client code.
The 2.0 model
Sanshain 2.0 replaced branches with producer-declared versions:
- The version lives in the spec file. For OpenAPI/AsyncAPI it is read from
info.version; proto files must carry a// sanshain-version: MAJOR.MINOR.PATCHcomment. Strict three-part semver, no suffixes. - Stability is declared on every provide. Every provide is a
snapshot(overwritable work-in-progress) unless the ga switch is set — then it isga(immutable; the number is permanently claimed). - Consumers pin exact versions. Each
requiresentry names the exactversionto build against. No ranges, nolatest, no fallback, no waiting.
Installation
Install from PyPI, into the same environment Conan runs in:
pip install sanshain-conan
Then import the helper directly in your conanfile.py:
from conan import ConanFile
from sanshainconan import Sanshain
class MyProject(ConanFile):
def generate(self):
sanshain = Sanshain(self)
# Download required API specs (runs during `conan install`)
sanshain.require()
# proceed with client generation and build
The 1.x/2.0
python_requires = "sanshain-conan/x.y.z"pattern is gone — it resolved from a Conan remote, and the package now ships on PyPI instead.
Configuration
The plugin uses the standard sanshain.yaml file in the project root:
sanshainUrl: "https://sanshain.example.com"
serviceName: "my-cpp-service"
provides:
- file: "openapi.yaml" # version read from its info.version
requires:
- serviceName: "other-service"
version: "1.2.0" # exact pin (MAJOR.MINOR.PATCH)
outputDirectory: "generated/sanshain"
endpoints:
- method: GET
path: /api/v1/user
sanshain.yaml carries no stability and no version for provides — the version travels inside the spec file (MAJOR[.MINOR[.PATCH]], optionally v-prefixed; omitted parts are zero and the stored form is always three-part), and stability is decided by the ga switch (below). Branch-era fields (branch, timeout, baseVersion, releaseBranches) are rejected at parse time with a migration hint.
Streams: trunk and release branches
Alongside stability, the pipeline declares which dependency graph its calls belong to. Like the
ga switch this is a property of the invocation and never appears in sanshain.yaml:
- Trunk CI sets
SANSHAIN_TRUNK=true(CLI:--trunk) — provides mark trunk's current version and requires feed the main graph. - Release and hotfix pipelines set
SANSHAIN_TAG=<branch>(CLI:--tag) — the calls update that sanshain-branch instead of trunk. The branch must already exist; an unknown one answers404.
Declaring both fails before any request is sent.
Retiring a protocol
Removing a provides entry tells Sanshain nothing — it cannot distinguish a dropped protocol
from a pipeline that stopped running. Keep the entry and mark it:
provides:
- apiType: asyncapi
retired: true
The next provide retires that family: the capability tag is cleared, it leaves the current
dependency graph, and its AsyncAPI channel contracts are released. Version history and existing
Consumer pins are untouched. Retiring needs the releaser role — which a release pipeline
already holds — or a maintainer grant on the Producer; publishing GA needs releaser too, and
a 403 names the role and the snapshot fallback.
⚠️ AsyncAPI 2.x perspective. Sanshain reads 2.x
publish/subscribefrom the application's perspective:publishmeans this service publishes to the channel,subscribemeans this service consumes it. The AsyncAPI 2.x specification defines those keywords from the client's perspective — exactly inverted. Sanshain deliberately uses the application-perspective reading because it matches the unambiguous 3.xsend/receivemapping. A document authored with the spec-literal reading registers its contracts, and has its subscriptions harvested, exactly backwards. Harvested subscriptions are printed after every AsyncAPI provide; those with drift or no publisher yet are warnings and never fail the build.
Stability: the ga switch
Every provide is a snapshot by default — a developer building locally can never accidentally release. ga is an explicit act:
- Environment variable:
SANSHAIN_GA=true(works for the CLI and the Conan integration) - CLI flag:
--ga
CI sets the switch on its protected-branch pipelines; that is the whole mechanism. There is no git detection and no branch matching.
# Local / feature pipeline: snapshot
python3 -m sanshainconan.cli provide
# Release pipeline: ga
python3 -m sanshainconan.cli --ga provide
# or: SANSHAIN_GA=true python3 -m sanshainconan.cli provide
Usage
Conan Integration
Import the installed package in your conanfile.py:
from conan import ConanFile
from sanshainconan import Sanshain
class MyProject(ConanFile):
def generate(self):
sanshain = Sanshain(self)
# Download required API specs (runs during `conan install`)
sanshain.require()
Why
generate()and notbuild()?Conan best practice recommends keeping
build()simple — it should only compile. Thegenerate()method runs duringconan installand is designed to prepare all build inputs (toolchain files, downloaded specs, generated code). Placingrequire()ingenerate()means developers can runconan install .followed by a native build (e.g.,cmake --build .) without needing Conan during the actual compilation step.Why is
provide()not in a Conan lifecycle method?Uploading your API spec to Sanshain is a publish action with side effects — it should only happen once after tests pass, typically as a CI step. Use the CLI:
CLI Tool
# Provide (upload) spec — run in CI after tests pass; add --ga on release pipelines
python3 -m sanshainconan.cli provide
# Require (download) specs — alternative to generate() integration
python3 -m sanshainconan.cli require
Provide Response Summary
After each successful provide, the CLI logs a human-readable summary:
✓ Provided 1.4.0 (snapshot): 2 new, 1 updated, 0 deleted endpoints
Re-providing byte-identical content is a no-op on the server (changes reports all zero), so CI re-runs of the same commit never fight.
Failure Modes
The 2.0 server fails immediately — nothing waits:
-
404Unknown: the producer or the pinned version does not exist (in either stability). A configuration error — fix theversionpin. List what exists:GET /producers/<serviceName>/versions. -
410Absent: the pinned version exists but deliberately does not include the requested endpoint(s). -
409Version conflict (provide): rejected by the version rules — e.g. re-providing an existing GA version with different content. The CLI surfaces the server's message and the proposed next free version:✗ Version conflict: GA 1.2.0 is immutable Publish as 1.3.0 — update info.version in specs/openapi.yamlThe plugin never modifies your spec files — bump the version yourself and republish.
-
Wrong server: if a provide/require fails and the instance turns out to be pre-2.0, the confusing error is replaced with
Sanshain server at <url> is <version>; this client requires Sanshain 2.x — upgrade the server.
Require-Side ETag Caching
The CLI stores the ETag from require responses and sends If-None-Match on subsequent runs. On 304 Not Modified:
⏭ user-service spec unchanged (304), skipping code generation.
A pin on a GA version can never change content; a pin on a snapshot can — which is exactly what the ETag detects.
State File
The local cache is stored at .sanshain-cache/state.json in the current working directory. The format is:
{
"requires": {
"user-service|1.2.0|GET|/api/v1/users": {
"etag": "\"sha256:def456...\"",
"last_fetched": "2026-04-25T12:00:00Z"
}
}
}
Strict Mode
By default, the CLI warns and skips when configuration is incomplete (no serviceName, no provides, no requires). This makes it safe to run both commands even if only one applies.
To fail on missing configuration, enable strict mode in sanshain.yaml:
strict: true
When strict mode is enabled:
- Missing
serviceName→ exit with error - No
providesconfigured → exit with error - No
requiresconfigured → exit with error
Note that a requires entry without a version, or any leftover branch-era field, is always a hard error — strict mode does not change that.
Features
- Exact Version Pins: Requires resolve immediately against the pinned version — GA preferred, else the same-numbered snapshot, else failure.
- Bundle Support: Downloads merged API specs for multiple endpoints with deduplicated schemas.
- Bearer Token Auth: Uses
SANSHAIN_TOKENenvironment variable. - GZIP Support: Efficiently downloads large specifications.
Development
Local Development with uv
It is recommended to use uv for managing the development environment.
-
Install uv: Follow the official installation guide.
-
Sync environment:
uv sync --dev
-
Run tests:
uv run python -m unittest discover tests
-
Linting and Checks:
uv run flake8 . uv run black --check . uv run bandit -r . uv run mypy .
-
Running Tools without Install (uvx):
uvx black .
Traditional Installation
If you prefer pip, you can still use it:
-
Install dependencies:
pip install -r requirements-dev.txt
-
Run tests:
export PYTHONPATH=$PYTHONPATH:. python3 -m unittest discover tests
License
This project is licensed under the Apache License 2.0. See the LICENSE file for details.
Metadata
Release files for sanshain-conan 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sanshain_conan-2.1.0.tar.gz | 63.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sanshain_conan-2.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.1 kB
Release files / sanshain_conan-2.1.0.tar.gz
| Download URL | sanshain_conan-2.1.0.tar.gz |
|---|---|
| Size | 63.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c5c0128cc97fd6f80330e6fbdc793b4b88a8d471034ddf17598031ded97dd143
|
|
BLAKE2b-256 checksum How to use checksums |
6550e39fd29f2adc487d4fbf9fc33500a6b3699752659b7b541c23f316697807
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.
Transparency logRelease files / sanshain_conan-2.1.0-py3-none-any.whl
| Download URL | sanshain_conan-2.1.0-py3-none-any.whl |
|---|---|
| Size | 22.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b244978fd4b21af3f8dc440daf4843ed6aa19c444f37b186aaeda77ce2dea660
|
|
BLAKE2b-256 checksum How to use checksums |
02e904b46be1ecc5d4cf64e4497c6d312d43fd9c35f74681a57237b357610acd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.
Transparency logSecurity for sanshain-conan 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sanshain_conan-2.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.