From e1ce3b007656cec8ec2d4e1d99f6d825284d7857 Mon Sep 17 00:00:00 2001 From: Alfred Hall Date: Tue, 14 Jul 2026 10:25:22 +0000 Subject: [PATCH 1/2] docs(examples): add runnable supplier bulk-add example (#4) First entry in examples/: add_suppliers.py loops client.suppliers.add(...) over a list (name, type, criticality, data access, CIA, markdown notes) and prints the assigned identifier + CIA per supplier. README covers install + env setup. CI now lints examples/ (ruff check src tests examples) so a published example can't silently rot when the SDK changes. Closes #4. --- .github/workflows/tests.yml | 4 ++- examples/README.md | 22 +++++++++++++ examples/add_suppliers.py | 62 +++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 examples/README.md create mode 100644 examples/add_suppliers.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c8df82f..21cf5ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,9 @@ jobs: python-version: "3.13" - run: pip install --quiet -e ".[dev]" - name: ruff - run: ruff check src tests + # examples/ is linted too so a published example can't silently rot + # (undefined names, bad imports) when the SDK changes. + run: ruff check src tests examples test: runs-on: ubuntu-latest diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..135dba6 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +# Examples + +## Setup + +```sh +pip install isms-sdk + +export ISMS_API_URL=https://your.isms.sh +export ISMS_API_TOKEN= +``` + +Or point at an env file (`KEY=VALUE` per line, same format the `isms` CLI reads): + +```sh +export ISMS_ENV=~/.isms/isms.env +``` + +## Run + +```sh +python examples/add_suppliers.py +``` diff --git a/examples/add_suppliers.py b/examples/add_suppliers.py new file mode 100644 index 0000000..242a7d0 --- /dev/null +++ b/examples/add_suppliers.py @@ -0,0 +1,62 @@ +from isms import IsmsClient, IsmsError + +client = IsmsClient.from_env() + +suppliers = [ + { + "name": "Anthropic", + "supplier_type": "saas", + "criticality": "high", + "data_access": True, + "contact": "https://trust.anthropic.com", + "confidentiality": 5, + "integrity": 4, + "availability": 4, + "notes": ( + "Claude API for AI-assisted drafting and evaluation across the " + "management system.\n\n" + "## Services\n" + "Claude API (Opus, Sonnet, Haiku) accessed over REST.\n\n" + "## Data protection\n" + "SOC 2 Type II. GDPR-compliant Data Processing Addendum with " + "Standard Contractual Clauses for EU-to-US transfers. Zero " + "data retention available on request for enterprise plans." + ), + }, + { + "name": "Microsoft", + "supplier_type": "saas", + "criticality": "high", + "data_access": True, + "contact": "https://servicetrust.microsoft.com", + "confidentiality": 5, + "integrity": 4, + "availability": 5, + "notes": ( + "Microsoft 365 tenant for email, collaboration, identity, and " + "business data storage.\n\n" + "## Services\n" + "Exchange Online, Teams, SharePoint, OneDrive, Entra ID.\n\n" + "## Data protection\n" + "ISO 27001, ISO 27017, ISO 27018, SOC 2 Type II. GDPR-compliant " + "DPA via Microsoft Product Terms with Standard Contractual " + "Clauses. EU-US Data Privacy Framework certified. EU data " + "residency available." + ), + }, +] + +# Re-running adds these again — it's a demo, not idempotent. +added = 0 +for s in suppliers: + try: + result = client.suppliers.add(s) + except IsmsError as e: + print(f"! {s['name']}: {e}") + continue + added += 1 + print(f"{result['identifier']} {result['name']} " + f"C{result['confidentiality']} I{result['integrity']} " + f"A{result['availability']}") + +print(f"\nAdded {added} of {len(suppliers)} suppliers.") From 1722d7ea26b263f82100b72d11db7cf99095f06f Mon Sep 17 00:00:00 2001 From: Alfred Hall Date: Tue, 14 Jul 2026 15:14:36 +0000 Subject: [PATCH 2/2] docs(examples): fictional suppliers, guard from_env, markdown notes (#5 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swap the real-vendor entries (Anthropic/Microsoft) for fictional suppliers (Acme Cloud SaaS, Globex Managed Hosting) — same fields, but no factual, time-sensitive compliance claims about real third parties that would drift out of date with nothing to catch it. - Guard IsmsClient.from_env() so a missing-env run exits with the error + a pointer to examples/README.md instead of a raw EnvironmentError traceback. - Move the markdown notes into readable triple-quoted constants instead of concatenated string fragments — the markdown reads as markdown in the source. --- examples/add_suppliers.py | 72 +++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/examples/add_suppliers.py b/examples/add_suppliers.py index 242a7d0..e77be00 100644 --- a/examples/add_suppliers.py +++ b/examples/add_suppliers.py @@ -1,48 +1,60 @@ +import sys + from isms import IsmsClient, IsmsError -client = IsmsClient.from_env() +try: + client = IsmsClient.from_env() +except EnvironmentError as e: + sys.exit(f"{e}\n\nSee examples/README.md for setup.") + +# Supplier notes are markdown — keep them as readable triple-quoted blocks rather +# than concatenated string fragments. Fictional vendors so the example doesn't +# assert real, time-sensitive compliance facts that would drift out of date. + +ACME_NOTES = """\ +Hosted CRM used by the sales team for customer records and pipeline data. + +## Services +CRM web app and REST API. + +## Data protection +SOC 2 Type II. GDPR-compliant Data Processing Addendum with Standard +Contractual Clauses for EU-to-US transfers. +""" + +GLOBEX_NOTES = """\ +Colocation and managed servers for the internal wiki and build infrastructure. + +## Services +Managed VMs, backups, and network. + +## Data protection +ISO 27001 certified. Annual penetration test; no access to production +customer data. +""" suppliers = [ { - "name": "Anthropic", + "name": "Acme Cloud SaaS", "supplier_type": "saas", "criticality": "high", "data_access": True, - "contact": "https://trust.anthropic.com", + "contact": "https://trust.acme-cloud.example", "confidentiality": 5, "integrity": 4, "availability": 4, - "notes": ( - "Claude API for AI-assisted drafting and evaluation across the " - "management system.\n\n" - "## Services\n" - "Claude API (Opus, Sonnet, Haiku) accessed over REST.\n\n" - "## Data protection\n" - "SOC 2 Type II. GDPR-compliant Data Processing Addendum with " - "Standard Contractual Clauses for EU-to-US transfers. Zero " - "data retention available on request for enterprise plans." - ), + "notes": ACME_NOTES, }, { - "name": "Microsoft", - "supplier_type": "saas", - "criticality": "high", - "data_access": True, - "contact": "https://servicetrust.microsoft.com", - "confidentiality": 5, + "name": "Globex Managed Hosting", + "supplier_type": "hosting", + "criticality": "medium", + "data_access": False, + "contact": "https://trust.globex.example", + "confidentiality": 3, "integrity": 4, "availability": 5, - "notes": ( - "Microsoft 365 tenant for email, collaboration, identity, and " - "business data storage.\n\n" - "## Services\n" - "Exchange Online, Teams, SharePoint, OneDrive, Entra ID.\n\n" - "## Data protection\n" - "ISO 27001, ISO 27017, ISO 27018, SOC 2 Type II. GDPR-compliant " - "DPA via Microsoft Product Terms with Standard Contractual " - "Clauses. EU-US Data Privacy Framework certified. EU data " - "residency available." - ), + "notes": GLOBEX_NOTES, }, ]