Skip to content

Repository files navigation

Graphiant SDK Python

PyPI versionPython 3.10+License: MITDocumentationCI/CD

A comprehensive Python SDK for Graphiant Network-as-a-Service (NaaS), with a built-in graphiant CLI for portal login and quick API calls.

More product and platform context: Graphiant Docs.

Table of contents

SectionWhat you’ll find
Documentation & linksOfficial guides, API reference, PyPI
FeaturesSDK + CLI at a glance
Quick startInstall, sign in, minimal Python example
Graphiant CLIFull CLI documentation (login, configure, invoke, rest, env vars)
Advanced usagePatterns and error handling
DevelopmentBuild, test, code generation
API reference (overview)Bundled OpenAPI, model docs, sample endpoints
SecurityAuth and environment variables
ContributingPR workflow
SupportLinks and contact

Documentation & links

ResourceLink
SDK guideGraphiant SDK Python
AutomationGraphiant Automation
REST APIGraphiant Portal REST API
Method index (repo)DefaultApi.md
OpenAPI bundle (this build)api/graphiant_api_docs_v26.7.0.json — source for generated paths and models
Model docs (*.md)docs/ (same names as Python classes, e.g. V1EdgesSummaryGetResponse.md)
PyPIgraphiant-sdk
ChangelogCHANGELOG.md

Features

  • Full REST coverage — Generated client for all Graphiant API endpoints.
  • Bearer authentication — Username/password login in code, or token from the CLI / GRAPHIANT_ACCESS_TOKEN.
  • Typed models — Pydantic models and type hints.
  • graphiant CLI — Portal login (Playwright), saved profiles, invoke / rest / whoami.

Quick start

1. Install

pip install graphiant-sdk

This provides both graphiant_sdk (Python) and the graphiant executable.

2. Sign in with the CLI

Complete login in the Chromium window that opens (or paste a token when prompted). Then load the token into your shell:

graphiant login
source~/.graphiant/env.sh
# or, without opening a browser (reads saved credentials):eval"$(graphiant login env-export)"

Confirm: echo $GRAPHIANT_ACCESS_TOKEN should show a long token.

See Graphiant CLI for options (--timeout, --no-capture, profiles, troubleshooting).

3. Basic Python usage

If GRAPHIANT_ACCESS_TOKEN is set—e.g. after graphiant login and sourcing env.sh—the client uses it and skips username/password login. Otherwise the example falls back to POST /v1/auth/login.

importosimportgraphiant_sdkfromgraphiant_sdk.exceptionsimport (
ApiException, BadRequestException, UnauthorizedException,
ForbiddenException, NotFoundException, ServiceException,
)
host=os.environ.get("GRAPHIANT_API_HOST", "https://api.graphiant.com")
access_token=os.environ.get("GRAPHIANT_ACCESS_TOKEN", "").strip()
config=graphiant_sdk.Configuration(host=host)
ifaccess_token:
# Use only authorization=… on each call — do not also set config.api_key for jwtAuth,# or the client may send two Authorization headers and gateways (e.g. Azure) can return 400.bearer_token=f"Bearer {access_token}"else:
config.username="your_username"config.password="your_password"api_client=graphiant_sdk.ApiClient(config)
api=graphiant_sdk.DefaultApi(api_client)
ifnotaccess_token:
auth_request=graphiant_sdk.V1AuthLoginPostRequest(
username=config.username,
password=config.password,
)
try:
auth_response=api.v1_auth_login_post(v1_auth_login_post_request=auth_request)
bearer_token=f"Bearer {auth_response.token}"print("Authentication successful")
exceptExceptionase:
print(f"Authentication failed: {e}")
exit(1)
# Get device summarytry:
edges_summary=api.v1_edges_summary_get(authorization=bearer_token)
print(f"Found {len(edges_summary.edges_summary)} devices")
fordeviceinedges_summary.edges_summary:
print(f"Device: {device.hostname}, Status: {device.status}")
exceptExceptionase:
print(f"Failed to get device summary: {e}")

Graphiant CLI

The graphiant command ships with graphiant-sdk. Use it to log in via the portal, store a bearer token, and run quick API checks without writing a full Python script.

  • Version:graphiant version matches graphiant_sdk.__version__.
  • Help:graphiant --help, graphiant login --help, etc.
  • Python usage: After source ~/.graphiant/env.sh, read GRAPHIANT_ACCESS_TOKEN in code — see §3 Basic Python usage.

Shell completion (bash, zsh, fish)

Tab completion is provided by Typer/Click but is not enabled until you install it once for your shell:

graphiant --install-completion

Then restart the terminal or source ~/.zshrc / ~/.bashrc. After that, graphiant <Tab> completes subcommands (e.g. login, rest, whoami) and options.

  • Inspect the script without modifying your config: graphiant --show-completion
  • zsh must run compinit (most frameworks do this already).
  • The package depends on shellingham so --install-completion can detect your shell.

If you prefer to wire zsh manually:

echo'eval "$(_GRAPHIANT_COMPLETE=zsh_source graphiant)"'>>~/.zshrc

Typical workflow

# 1) Log in (browser opens; paste token if prompted)
graphiant login
# 2) Load the token into *this* shell (required — see below)source~/.graphiant/env.sh
# 3) Sanity-check session
graphiant whoami
# 4) Call an API (token from env or saved profile)
graphiant invoke v1_edges_summary_get
graphiant rest GET /v1/edges-summary

graphiant login

Command / optionWhat it does
graphiant loginOpens Chromium (Playwright). Observes /v1/…, /v2/…, and …/auth/refresh traffic and captures Authorization: Bearer … (and refresh JSON when applicable). After first real token, loads portal / and reloads once.
--portal-url <url>Portal base URL for this run (default: config or https://portal.graphiant.com/).
-t, --timeout <sec>Wait for capture (default 90). Then prompt to paste. Ctrl+C skips to paste.
--no-captureNo Playwright; open portal and paste token (full Authorization value including Bearer, or raw JWT).
--no-browserPrint portal URL only; paste when prompted.
--profile <name>Store token under named profile (default default).
--export / --no-exportAfter success, also print export GRAPHIANT_ACCESS_TOKEN=… to stdout for scripts (default: off, so the token is not echoed). ~/.graphiant/env.sh is always written.
-v, --verboseDebug logging on stderr. Or GRAPHIANT_LOG=debug / info / warning.
graphiant login env-exportPrint one export … line to stdout (no browser). Use: eval "$(graphiant login env-export)".

Paste / DevTools: If auto-capture fails, copy the fullAuthorization header from Network (including the word Bearer). The CLI does not read DevTools; it only listens inside its own Chromium session.

If GRAPHIANT_ACCESS_TOKEN is empty after login: The token is saved under ~/.graphiant/ in env.sh. In this terminal, run source ~/.graphiant/env.sh or eval "$(graphiant login env-export)". You can also chain: graphiant login && source ~/.graphiant/env.sh (add your usual login flags before &&). New IDE terminals don’t inherit another tab’s source unless you reload it there too.

graphiant configure

CommandPurpose
graphiant configure set-host <url>Default API base URL (e.g. https://api.graphiant.com).
graphiant configure set-portal-url <url>Default portal URL for future logins.
graphiant configure showShow host, portal, profile, token presence.

Call the API from the terminal

Exact method names match DefaultApi in the SDK — see DefaultApi.md or list locally:

graphiant api list --prefix v1_auth_ # table: SDK method, HTTP, path
graphiant apis --plain --prefix v1_auth_ # one SDK method name per line
graphiant invoke v1_auth_get
graphiant api invoke v1_edges_summary_get
graphiant invoke v1_edges_summary_get --kwargs '{"enterprise_id": 123}'

graphiant invoke sends a singleAuthorization header (the generated client’s authorization parameter only). It does not also apply jwtAuth from Configuration, so gateways that reject duplicate auth headers (for example Azure Application Gateway) accept the request.

Query parameters and filters

  • graphiant invoke / graphiant api invoke — Uses the generated DefaultApi method signature. Anything that is a query string in REST becomes a keyword argument on that method, named in snake_case (OpenAPI enterpriseIdenterprise_id). Pass them inside --kwargs as JSON. You do not pass authorization manually; the CLI fills Bearer <token> for you.

    graphiant invoke v1_edges_summary_get --kwargs '{"enterprise_id": 123, "is_requested": true}'

    Optional arguments can be omitted. For positional parameters (rare), use --args with a JSON array in parameter order; the first slot is usually authorization, which the CLI injects if you skip it by using --kwargs only.

  • POST / PATCH with a JSON body — Bodies use the same keyword names as DefaultApi (often a single v1_*_post_request argument whose JSON matches the Pydantic model). See docs/<ModelName>.md for fields.

    graphiant invoke v1_edges_summary_post --kwargs '{"v1_edges_summary_post_request": {"filter": {}}}'
    graphiant invoke v1_global_summary_post --kwargs '{"v1_global_summary_post_request": {"ntpType": true}}'
    graphiant invoke v1_global_content_filters_get
    graphiant invoke v1_global_domain_categories_get
  • graphiant rest — Query strings are a single --query / -q string: key=value pairs joined with &. Values are strings (URL-encode special characters in the shell if needed).

    graphiant rest GET /v1/edges-summary --query 'enterpriseId=123&isRequested=true'

Raw HTTP (path under configured API host):

graphiant rest GET /v1/edges-summary
graphiant rest GET /v1/devices/1234567890123
graphiant rest POST /v1/global/summary --body '{"ntpType": true}'
CommandPurpose
graphiant whoamiGET /v1/auth/user and GET /v1/users?id=…; Rich tables (session, permissions, profile). lastActiveAt (and similar protobuf timestamps) are shown in UTC, labeled (UTC).
graphiant logoutClear stored profile (see --profile). Your shell may still export GRAPHIANT_ACCESS_TOKEN — run unset GRAPHIANT_ACCESS_TOKEN in that terminal if needed.
graphiant versionPrint CLI and package version.

Environment variables & files

Path / variableRole
~/.graphiant/config.jsonDefault API host and portal URL.
~/.graphiant/credentials.jsonProfiles and stored access tokens.
~/.graphiant/env.shexport GRAPHIANT_ACCESS_TOKEN=… after each successful login.
GRAPHIANT_CONFIG_DIROverride config directory (default ~/.graphiant).
GRAPHIANT_ACCESS_TOKENIf set, preferred over disk token for CLI/SDK in that environment.
GRAPHIANT_API_HOSTFallback API host when not in config.
GRAPHIANT_PROFILEActive profile name (default default).
GRAPHIANT_LOGLogin log level: debug, info, warning.

Capture behavior & troubleshooting

  • Listeners use Playwright’s sync driver; the wait loop uses page.wait_for_timeout so request / response events are processed (plain sleep can miss tokens until too late).
  • Placeholder values such as Bearer null on /v1/auth/login/pre are ignored; the CLI waits for a real session token.
  • If capture times out: graphiant login -t 180, complete SSO sooner, F5 on the portal home, graphiant login --no-capture, or graphiant login -v / GRAPHIANT_LOG=debug.

CLI dependencies

Typer, Rich, and Playwright (Chromium installed on first use if missing via playwright install chromium).

Package layout (graphiant_cli/, for contributors)

ModuleRole
main.pyTyper app: login, configure, api, rest, whoami (GET /v1/auth/user), …
browser_capture.pyPlaywright session and network capture
token_parsing.pyHeaders, JSON, URL matching, token validation
login_common.pySave credentials, user-facing success text, stdout export
portal_login.pyPortal URL helpers, open_portal, test re-exports
config_store.py~/.graphiant/ persistence
cli_logging.pyLogging (no secrets in logs)
sdk_invoke.py, rest_client.pySDK invoke and raw REST

🔧 Advanced Usage

Device Configuration Management

# Verify device portal status before configurationdefverify_device_portal_status(api, bearer_token, device_id):
"""Verify device is ready for configuration updates"""edges_summary=api.v1_edges_summary_get(authorization=bearer_token)
foredgeinedges_summary.edges_summary:
ifedge.device_id==device_id:
ifedge.portal_status=="Ready"andedge.tt_conn_count==2:
returnTrueelse:
raiseException(f"Device {device_id} not ready. "f"Status: {edge.portal_status}, "f"TT Connections: {edge.tt_conn_count}")
returnFalse# Configure device interfacesdefconfigure_device_interfaces(api, bearer_token, device_id):
"""Configure device interfaces with circuits and subinterfaces"""# Define circuitscircuits= {
"c-gigabitethernet5-0-0": {
"name": "c-gigabitethernet5-0-0",
"description": "c-gigabitethernet5-0-0",
"linkUpSpeedMbps": 50,
"linkDownSpeedMbps": 100,
"connectionType": "internet_dia",
"label": "internet_dia_4",
"qosProfile": "gold25",
"qosProfileType": "balanced",
"diaEnabled": False,
"lastResort": False,
"patAddresses": {},
"staticRoutes": {}
}
}
# Define interfacesinterfaces= {
"GigabitEthernet5/0/0": {
"interface": {
"adminStatus": True,
"maxTransmissionUnit": 1500,
"circuit": "c-gigabitethernet5-0-0",
"description": "wan_1",
"alias": "primary_wan",
"ipv4": {"dhcp": {"dhcpClient": True}},
"ipv6": {"dhcp": {"dhcpClient": True}}
}
},
"GigabitEthernet8/0/0": {
"interface": {
"subinterfaces": {
"18": {
"interface": {
"lan": "lan-7-test",
"vlan": 18,
"description": "lan-7",
"alias": "non_production",
"adminStatus": True,
"ipv4": {"address": {"address": "10.2.7.1/24"}},
"ipv6": {"address": {"address": "2001:10:2:7::1/64"}}
}
}
}
}
}
}
# Create configuration requestedge_config=graphiant_sdk.ManaV2EdgeDeviceConfig(
circuits=circuits,
interfaces=interfaces
)
config_request=graphiant_sdk.V1DevicesDeviceIdConfigPutRequest(
edge=edge_config
)
try:
# Verify device is readyverify_device_portal_status(api, bearer_token, device_id)
# Push configurationresponse=api.v1_devices_device_id_config_put(
authorization=bearer_token,
device_id=device_id,
v1_devices_device_id_config_put_request=config_request
)
print(f"Configuration job submitted. Job ID: {response.job_id}")
returnresponseexceptForbiddenExceptionase:
print(f"Permission denied: {e}")
exceptExceptionase:
print(f"Configuration failed: {e}")

Error Handling

defhandle_api_errors(func):
"""Decorator for consistent error handling"""defwrapper(*args, **kwargs):
try:
returnfunc(*args, **kwargs)
exceptBadRequestExceptionase:
print(f"Bad Request: {e}")
exceptUnauthorizedExceptionase:
print(f"Unauthorized: {e}")
exceptForbiddenExceptionase:
print(f"Forbidden: {e}")
exceptNotFoundExceptionase:
print(f"Not Found: {e}")
exceptServiceExceptionase:
print(f"Service Error: {e}")
exceptApiExceptionase:
print(f"API Error: {e}")
returnwrapper@handle_api_errorsdefget_device_info(api, bearer_token, device_id):
"""Get detailed device information"""returnapi.v1_devices_device_id_get(
authorization=bearer_token,
device_id=device_id
)

🛠️ Development

Prerequisites

  • Python 3.10+ (3.13 recommended)
  • Git
  • OpenAPI Generator >= 7.23.0 (for code generation) — brew install openapi-generator or npm install -g @openapitools/openapi-generator-cli

CI/CD Workflows

This repository uses GitHub Actions for continuous integration and deployment:

  • Linting (lint.yml): Runs Flake8 and MyPy type checking on pull requests and pushes
  • Testing (test.yml): Runs pytest with coverage across Python 3.10, 3.11, 3.12, and 3.13; coverage uploaded on 3.13
  • Building (build.yml): Builds wheel and source distributions
  • Releasing (release.yml): Publishes to PyPI (manual trigger, admin-only)

See .github/workflows/README.md for detailed workflow documentation.

Building from Source

# Clone repository
git clone git@github.com:Graphiant-Inc/graphiant-sdk-python.git
cd graphiant-sdk-python
# Create virtual environment and install with dev dependencies
python3 -m venv venv &&source venv/bin/activate
make install # pip install -e ".[dev]"# Run tests
make test# pytest --cov=graphiant_sdk ...# Lint (hand-written files only; generated models excluded via .flake8)
make lint
# Type check (generated models excluded via pyproject.toml)
make type-check
# Build wheel and source distribution
make build # python -m build

Or without make:

pip install -e ".[dev]"&& pytest --cov=graphiant_sdk

Code Generation

To regenerate the SDK from the latest API specification:

# Quickest path — uses scripts/generate.sh with api/openapi.yaml
make generate
# Or run the script directly (supports OPENAPI_SPEC override)
OPENAPI_SPEC=api/graphiant_api_docs_v26.7.0.json bash scripts/generate.sh

scripts/generate.sh wraps the full openapi-generator-cli generate invocation (requires OpenAPI Generator >= 7.23.0). It auto-detects openapi-generator (Homebrew) or openapi-generator-cli (npm), reads the current version from pyproject.toml, and passes --git-user-id/--git-repo-id so generated docs never contain GIT_USER_ID placeholders.

Note: Download the latest API bundle from the Graphiant portal under Support HubDeveloper Tools and place it in api/. The versioned JSON bundle (api/graphiant_api_docs_v26.7.0.json) is the snapshot used for this release; api/openapi.yaml is the primary YAML spec. Hand-written files listed in .openapi-generator-ignore (graphiant_cli/, graphiant_sdk/api_client.py, configuration.py, etc.) are never overwritten by the generator.

Testing

make test# pytest --cov=graphiant_sdk --cov-report=term --cov-report=xml# Or directly:
pytest -v tests/
pytest tests/ --cov=graphiant_sdk --cov-report=html

📖 API Reference

Source of truth (this release)

Operations and schemas are generated from api/graphiant_api_docs_v26.7.0.json (in api/ and bundled in the PyPI wheel). For a newer portal/API, download the current bundle (Support Hub → Developer Tools) and diff paths before relying on URLs here.

How to exploreWhere
Every operation (method, path, parameters)docs/DefaultApi.md
CLI: SDK name + HTTP + pathgraphiant api list or graphiant apis --prefix v1_
Request/response field listsdocs/*.md — file basename matches the Python model (e.g. V1EdgesSummaryGetResponse.md)
Python importsfrom graphiant_sdk import … or graphiant_sdk.models

REST query parameters use camelCase in URLs (enterpriseId). Generated Python kwargs use snake_case (enterprise_id, device_id). Path templates below follow OpenAPI ({deviceId}).

Core classes

  • Configuration — API host, timeouts; do not set api_key["jwtAuth"] when every call passes authorization= (avoids duplicate Authorization headers on strict gateways).
  • ApiClient — HTTP client used by DefaultApi.
  • DefaultApi — one method per operation (e.g. v1_edges_summary_getGET/v1/edges-summary).

Example SDK models (verified in this package)

Model (import graphiant_sdk or graphiant_sdk.models)Typical operation
V1AuthLoginPostRequest, V1AuthLoginPostResponsePOST /v1/auth/login
V1AuthUserGetResponseGET /v1/auth/user
V1EdgesSummaryGetResponseGET /v1/edges-summary
V1EdgesSummaryPostRequest (optional filter)POST /v1/edges-summary
V1DevicesDeviceIdGetResponseGET /v1/devices/{deviceId}
V1DevicesDeviceIdConfigPutRequest, V1DevicesDeviceIdConfigPutResponsePUT /v1/devices/{deviceId}/config (job accepted; no GET on …/config in this spec)
ManaV2EdgeDeviceConfigNested edge object inside V1DevicesDeviceIdConfigPutRequest
V1GlobalSummaryPostRequest, V1GlobalSummaryPostResponsePOST /v1/global/summary
V2ParentalertlistPostRequest, V2ParentalertlistPostResponsePOST /v2/parentalertlist

Sample HTTP endpoints (from graphiant_api_docs_v26.7.0.json)

The API surface is large; this table lists real paths from the bundled spec. For the full set, use graphiant api list or DefaultApi.md.

EndpointMethodExample DefaultApi methodNotes
/v1/auth/loginPOSTv1_auth_login_postBody: V1AuthLoginPostRequest
/v1/auth/userGETv1_auth_user_getSession user
/v1/usersGETv1_users_gete.g. id query (see graphiant whoami)
/v1/edges-summaryGETv1_edges_summary_getQueries e.g. enterpriseId, isRequested
/v1/edges-summaryPOSTv1_edges_summary_postBody: V1EdgesSummaryPostRequest
/v1/devices/{deviceId}GETv1_devices_device_id_getDevice detail
/v1/devices/{deviceId}/configPUTv1_devices_device_id_config_putBody: V1DevicesDeviceIdConfigPutRequest
/v1/global/summaryPOSTv1_global_summary_postBody: V1GlobalSummaryPostRequest
/v1/sites/{siteId}/circuitsGETv1_sites_site_id_circuits_getCircuits for a site
/v2/parentalertlistPOSTv2_parentalertlist_postBody: V2ParentalertlistPostRequest

🔐 Security

  • Authentication: Bearer token-based authentication
  • HTTPS: All API communications use HTTPS
  • Credentials: Store credentials securely using environment variables
  • Token Management: Bearer tokens expire and should be refreshed as needed

Environment Variables

export GRAPHIANT_HOST="https://api.graphiant.com"export GRAPHIANT_USERNAME="your_username"export GRAPHIANT_PASSWORD="your_password"
importosusername=os.getenv("GRAPHIANT_USERNAME")
password=os.getenv("GRAPHIANT_PASSWORD")
host=os.getenv("GRAPHIANT_HOST", "https://api.graphiant.com")

Note: For detailed security policies, vulnerability reporting, and security best practices, see SECURITY.md.

🤝 Contributing

We welcome contributions! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes and ensure they pass local checks:
    make test# run tests with coverage
    make lint # flake8 on hand-written files (generated excluded via .flake8)
    make type-check # mypy (generated models excluded via pyproject.toml)
  4. Commit your changes with a clear message (git commit -m 'Add amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. Open a Pull Request

Note: All pull requests automatically run CI/CD checks (linting, testing across multiple Python versions). Ensure all checks pass before requesting review.

See CONTRIBUTING.md for detailed contribution guidelines.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🆘 Support

🔗 Related Projects


Made with ❤️ by the Graphiant Team

About

Python SDK for Graphiant NaaS

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages