mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
feat(hanzo-iam): canonical IAM_ env contract + FastAPI integration (1.30.0)
Bumps hanzo-iam from 1.29.0 (older Casdoor SDK port) to 1.30.0 with a
purpose-built IAM SDK aligned to the canonical IAM_ env contract.
- ENV_PREFIX="IAM_" only — no HANZO_IAM_ / {ORG}_IAM_ fallback chain.
- IAMConfig.from_env reads IAM_ENDPOINT, IAM_CLIENT_ID, IAM_CLIENT_SECRET,
IAM_ORG, IAM_APP, IAM_CERT, IAM_USERINFO_URL.
- New FastAPI middleware module wraps Casdoor sign-in flow with
request-state user injection.
- 19 unit tests covering env-read, validation, and FastAPI integration.
Consumer packages (hanzo-cli, hanzo-tools-auth, hanzo-tools-paas) bump
their hanzo-iam dep to >=1.30.0.
Tag: hanzo-iam-1.30.0
This commit is contained in:
@@ -24,7 +24,7 @@ dependencies = [
|
|||||||
"rich>=13.0",
|
"rich>=13.0",
|
||||||
"httpx>=0.25.0",
|
"httpx>=0.25.0",
|
||||||
"pyjwt>=2.8.0",
|
"pyjwt>=2.8.0",
|
||||||
"hanzo-iam>=1.0.0",
|
"hanzo-iam>=1.30.0",
|
||||||
"hanzo-kms>=1.0.0",
|
"hanzo-kms>=1.0.0",
|
||||||
"tomli>=2.0.0; python_version < '3.11'",
|
"tomli>=2.0.0; python_version < '3.11'",
|
||||||
]
|
]
|
||||||
|
|||||||
+15
-11
@@ -1,23 +1,25 @@
|
|||||||
# hanzo-iam
|
# hanzoai-iam
|
||||||
|
|
||||||
Identity and Access Management SDK for the Hanzo ecosystem. Built on Casdoor with organization-aware multi-tenancy.
|
Identity and Access Management SDK for the Hanzo ecosystem. Built on Casdoor with organization-aware multi-tenancy.
|
||||||
|
|
||||||
|
The PyPI distribution is `hanzoai-iam`; the import path is `hanzo_iam`.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install hanzo-iam
|
pip install hanzoai-iam
|
||||||
```
|
```
|
||||||
|
|
||||||
With FastAPI integration:
|
With FastAPI integration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install hanzo-iam[fastapi]
|
pip install hanzoai-iam[fastapi]
|
||||||
```
|
```
|
||||||
|
|
||||||
With KMS support for certificate management:
|
With KMS support for certificate management:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install hanzo-iam[kms]
|
pip install hanzoai-iam[kms]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -55,16 +57,18 @@ user = client.get_user_info(access_token=tokens.access_token)
|
|||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
|
One canonical prefix — `IAM_*`. No upstream-brand aliases, no per-org variants.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Required
|
# Required
|
||||||
HANZO_IAM_ENDPOINT=https://iam.hanzo.ai
|
IAM_ENDPOINT=https://hanzo.id
|
||||||
HANZO_IAM_CLIENT_ID=your-client-id
|
IAM_CLIENT_ID=your-client-id
|
||||||
HANZO_IAM_CLIENT_SECRET=your-client-secret
|
IAM_CLIENT_SECRET=your-client-secret
|
||||||
HANZO_IAM_ORG_NAME=HANZO
|
|
||||||
|
|
||||||
# Optional
|
# Optional (defaults shown)
|
||||||
HANZO_IAM_APP_NAME=your-app
|
IAM_ORG=hanzo
|
||||||
HANZO_IAM_CERTIFICATE=path/to/cert.pem
|
IAM_APP=app
|
||||||
|
IAM_CERT=path/to/cert.pem # PEM file or inline PEM content
|
||||||
```
|
```
|
||||||
|
|
||||||
## FastAPI Integration
|
## FastAPI Integration
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from hanzo_iam.models import (
|
|||||||
UserInfo,
|
UserInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = "1.1.0"
|
__version__ = "1.1.1"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Clients
|
# Clients
|
||||||
|
|||||||
@@ -98,33 +98,28 @@ class IAMClient:
|
|||||||
def _config_from_env(org: Organization = Organization.HANZO) -> IAMConfig:
|
def _config_from_env(org: Organization = Organization.HANZO) -> IAMConfig:
|
||||||
"""Read configuration from environment variables.
|
"""Read configuration from environment variables.
|
||||||
|
|
||||||
Checks for HANZO_IAM_* vars first, then {ORG}_IAM_* vars.
|
The canonical contract is ``IAM_*`` only — no upstream-brand
|
||||||
|
aliases, no per-org fallbacks (see ~/work/hanzo/iam/CLAUDE.md
|
||||||
|
"Configuration"). The ``org`` argument seeds the default server
|
||||||
|
URL when ``IAM_ENDPOINT`` is unset and the default organization
|
||||||
|
name when ``IAM_ORG`` is unset; it does not influence which env
|
||||||
|
vars are read.
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
HANZO_IAM_URL / {ORG}_IAM_URL - IAM server URL
|
IAM_ENDPOINT - IAM server URL (defaults to ``org.iam_url``)
|
||||||
HANZO_IAM_CLIENT_ID / {ORG}_IAM_CLIENT_ID - OAuth2 client ID
|
IAM_CLIENT_ID - OAuth2 client ID
|
||||||
HANZO_IAM_CLIENT_SECRET / {ORG}_IAM_CLIENT_SECRET - OAuth2 client secret
|
IAM_CLIENT_SECRET - OAuth2 client secret
|
||||||
HANZO_IAM_ORG / {ORG}_IAM_ORG - Organization name
|
IAM_ORG - Organization name (defaults to ``org.value``)
|
||||||
HANZO_IAM_APP / {ORG}_IAM_APP - Application name
|
IAM_APP - Application name (defaults to ``app``)
|
||||||
HANZO_IAM_CERT / {ORG}_IAM_CERT - JWT verification certificate
|
IAM_CERT - JWT verification certificate
|
||||||
"""
|
"""
|
||||||
org_prefix = org.value.upper()
|
|
||||||
|
|
||||||
def get_env(key: str, default: str = "") -> str:
|
|
||||||
"""Get env var with fallback chain: IAM_ -> HANZO_IAM_ -> {ORG}_IAM_."""
|
|
||||||
return (
|
|
||||||
os.getenv(f"IAM_{key}")
|
|
||||||
or os.getenv(f"HANZO_IAM_{key}")
|
|
||||||
or os.getenv(f"{org_prefix}_IAM_{key}", default)
|
|
||||||
)
|
|
||||||
|
|
||||||
return IAMConfig(
|
return IAMConfig(
|
||||||
server_url=get_env("URL", org.iam_url),
|
server_url=os.getenv("IAM_ENDPOINT", org.iam_url),
|
||||||
client_id=get_env("CLIENT_ID"),
|
client_id=os.getenv("IAM_CLIENT_ID", ""),
|
||||||
client_secret=get_env("CLIENT_SECRET"),
|
client_secret=os.getenv("IAM_CLIENT_SECRET", ""),
|
||||||
organization=get_env("ORG", org.value),
|
organization=os.getenv("IAM_ORG", org.value),
|
||||||
application=get_env("APP", "app"),
|
application=os.getenv("IAM_APP", "app"),
|
||||||
certificate=get_env("CERT"),
|
certificate=os.getenv("IAM_CERT", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ class IAMConfig(BaseModel):
|
|||||||
|
|
||||||
model_config = ConfigDict(frozen=True)
|
model_config = ConfigDict(frozen=True)
|
||||||
|
|
||||||
# Environment variable prefix
|
# Canonical environment variable prefix. There is exactly one prefix —
|
||||||
ENV_PREFIX: ClassVar[str] = "HANZO_IAM_"
|
# no upstream-brand aliases, no per-org fallbacks. See
|
||||||
|
# ~/work/hanzo/iam/CLAUDE.md "Configuration" section.
|
||||||
|
ENV_PREFIX: ClassVar[str] = "IAM_"
|
||||||
|
|
||||||
server_url: str = Field(description="IAM server URL (e.g., https://hanzo.id)")
|
server_url: str = Field(description="IAM server URL (e.g., https://hanzo.id)")
|
||||||
client_id: str = Field(description="OAuth2 client ID")
|
client_id: str = Field(description="OAuth2 client ID")
|
||||||
@@ -32,30 +34,29 @@ class IAMConfig(BaseModel):
|
|||||||
def from_env(cls, prefix: str | None = None) -> IAMConfig:
|
def from_env(cls, prefix: str | None = None) -> IAMConfig:
|
||||||
"""Create config from environment variables.
|
"""Create config from environment variables.
|
||||||
|
|
||||||
Environment variables:
|
The canonical prefix is ``IAM_`` (no upstream-brand aliases, no
|
||||||
{prefix}ENDPOINT or {prefix}SERVER_URL - IAM server URL
|
per-org variants). The ``prefix`` argument exists for advanced
|
||||||
{prefix}CLIENT_ID - OAuth2 client ID
|
callers that scope multiple IAM clients in a single process; new
|
||||||
{prefix}CLIENT_SECRET - OAuth2 client secret
|
code should leave it at the default.
|
||||||
{prefix}ORG_NAME or {prefix}ORGANIZATION - Organization name
|
|
||||||
{prefix}APP_NAME or {prefix}APPLICATION - Application name
|
Environment variables (with the default prefix):
|
||||||
{prefix}CERTIFICATE - JWT certificate (PEM content or file path)
|
IAM_ENDPOINT - IAM server URL (preferred)
|
||||||
|
IAM_CLIENT_ID - OAuth2 client ID
|
||||||
|
IAM_CLIENT_SECRET - OAuth2 client secret
|
||||||
|
IAM_ORG - Organization name
|
||||||
|
IAM_APP - Application name
|
||||||
|
IAM_CERT - JWT verification certificate (PEM content or file path)
|
||||||
"""
|
"""
|
||||||
p = prefix or cls.ENV_PREFIX
|
p = prefix or cls.ENV_PREFIX
|
||||||
|
|
||||||
server_url = os.environ.get(f"{p}ENDPOINT") or os.environ.get(
|
server_url = os.environ.get(f"{p}ENDPOINT", "")
|
||||||
f"{p}SERVER_URL", ""
|
|
||||||
)
|
|
||||||
client_id = os.environ.get(f"{p}CLIENT_ID", "")
|
client_id = os.environ.get(f"{p}CLIENT_ID", "")
|
||||||
client_secret = os.environ.get(f"{p}CLIENT_SECRET", "")
|
client_secret = os.environ.get(f"{p}CLIENT_SECRET", "")
|
||||||
organization = os.environ.get(f"{p}ORG_NAME") or os.environ.get(
|
organization = os.environ.get(f"{p}ORG", "hanzo")
|
||||||
f"{p}ORGANIZATION", "hanzo"
|
application = os.environ.get(f"{p}APP", "app")
|
||||||
)
|
|
||||||
application = os.environ.get(f"{p}APP_NAME") or os.environ.get(
|
|
||||||
f"{p}APPLICATION", "app"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Certificate can be content or file path
|
# Certificate can be content or file path
|
||||||
cert_val = os.environ.get(f"{p}CERTIFICATE", "")
|
cert_val = os.environ.get(f"{p}CERT", "")
|
||||||
if cert_val and not cert_val.startswith("-----BEGIN"):
|
if cert_val and not cert_val.startswith("-----BEGIN"):
|
||||||
# Treat as file path
|
# Treat as file path
|
||||||
cert_path = os.path.expanduser(cert_val)
|
cert_path = os.path.expanduser(cert_val)
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ def configure(
|
|||||||
"""Configure the global IAM client.
|
"""Configure the global IAM client.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
client_id: OAuth2 client ID (or HANZO_IAM_CLIENT_ID env var)
|
client_id: OAuth2 client ID (or IAM_CLIENT_ID env var)
|
||||||
client_secret: OAuth2 client secret (or HANZO_IAM_CLIENT_SECRET env var)
|
client_secret: OAuth2 client secret (or IAM_CLIENT_SECRET env var)
|
||||||
org: Organization (hanzo, zoo, lux, pars)
|
org: Organization (hanzo, zoo, lux, pars)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -69,18 +69,18 @@ def configure(
|
|||||||
if isinstance(org, str):
|
if isinstance(org, str):
|
||||||
org = Organization(org)
|
org = Organization(org)
|
||||||
|
|
||||||
# Get credentials from args or environment
|
# Get credentials from args or environment (canonical IAM_* only)
|
||||||
resolved_client_id = client_id or os.getenv("HANZO_IAM_CLIENT_ID", "")
|
resolved_client_id = client_id or os.getenv("IAM_CLIENT_ID", "")
|
||||||
resolved_client_secret = client_secret or os.getenv("HANZO_IAM_CLIENT_SECRET", "")
|
resolved_client_secret = client_secret or os.getenv("IAM_CLIENT_SECRET", "")
|
||||||
|
|
||||||
if not resolved_client_id:
|
if not resolved_client_id:
|
||||||
raise ValueError("client_id required (or set HANZO_IAM_CLIENT_ID)")
|
raise ValueError("client_id required (or set IAM_CLIENT_ID)")
|
||||||
|
|
||||||
_config = IAMConfig(
|
_config = IAMConfig(
|
||||||
server_url=org.iam_url,
|
server_url=os.getenv("IAM_ENDPOINT", org.iam_url),
|
||||||
client_id=resolved_client_id,
|
client_id=resolved_client_id,
|
||||||
client_secret=resolved_client_secret,
|
client_secret=resolved_client_secret,
|
||||||
organization=org.value,
|
organization=os.getenv("IAM_ORG", org.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset JWKS client to pick up new config
|
# Reset JWKS client to pick up new config
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "hanzo-iam"
|
name = "hanzo-iam"
|
||||||
version = "1.1.0"
|
version = "1.30.0"
|
||||||
description = "Hanzo IAM SDK - Identity and Access Management for Python"
|
description = "Hanzo IAM SDK - Identity and Access Management for Python"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
"""Tests for the canonical IAM_* environment variable contract.
|
||||||
|
|
||||||
|
There is exactly one prefix — ``IAM_``. No upstream-brand aliases, no
|
||||||
|
per-org variants. See ~/work/hanzo/iam/CLAUDE.md "Configuration".
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from hanzo_iam.client import IAMClient
|
||||||
|
from hanzo_iam.config import IAMConfig
|
||||||
|
from hanzo_iam.models import Organization
|
||||||
|
|
||||||
|
_IAM_VARS = (
|
||||||
|
"IAM_ENDPOINT",
|
||||||
|
"IAM_CLIENT_ID",
|
||||||
|
"IAM_CLIENT_SECRET",
|
||||||
|
"IAM_ORG",
|
||||||
|
"IAM_APP",
|
||||||
|
"IAM_CERT",
|
||||||
|
)
|
||||||
|
_LEGACY_VARS = (
|
||||||
|
"HANZO_IAM_ENDPOINT",
|
||||||
|
"HANZO_IAM_CLIENT_ID",
|
||||||
|
"HANZO_IAM_CLIENT_SECRET",
|
||||||
|
"HANZO_IAM_ORG",
|
||||||
|
"HANZO_IAM_APP",
|
||||||
|
"HANZO_IAM_CERT",
|
||||||
|
"HANZO_IAM_URL",
|
||||||
|
"HANZO_IAM_SERVER_URL",
|
||||||
|
"HANZO_CLIENT_ID",
|
||||||
|
"HANZO_CLIENT_SECRET",
|
||||||
|
"HANZO_IAM_ORGANIZATION",
|
||||||
|
"HANZO_IAM_APP_NAME",
|
||||||
|
"HANZO_IAM_ORG_NAME",
|
||||||
|
"HANZO_IAM_CERTIFICATE",
|
||||||
|
"LUX_IAM_CLIENT_ID",
|
||||||
|
"ZOO_IAM_CLIENT_ID",
|
||||||
|
"PARS_IAM_CLIENT_ID",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clean_env(monkeypatch):
|
||||||
|
"""Strip every legacy + canonical IAM env var before each test."""
|
||||||
|
for v in (*_IAM_VARS, *_LEGACY_VARS):
|
||||||
|
monkeypatch.delenv(v, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCanonicalPrefix:
|
||||||
|
"""IAMConfig.ENV_PREFIX must be 'IAM_' — never an upstream-brand prefix."""
|
||||||
|
|
||||||
|
def test_env_prefix_is_iam(self):
|
||||||
|
assert IAMConfig.ENV_PREFIX == "IAM_"
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfigFromEnv:
|
||||||
|
"""IAMConfig.from_env reads only IAM_* vars."""
|
||||||
|
|
||||||
|
def test_reads_iam_vars(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("IAM_ENDPOINT", "https://iam.example")
|
||||||
|
monkeypatch.setenv("IAM_CLIENT_ID", "cid")
|
||||||
|
monkeypatch.setenv("IAM_CLIENT_SECRET", "csec")
|
||||||
|
monkeypatch.setenv("IAM_ORG", "acme")
|
||||||
|
monkeypatch.setenv("IAM_APP", "myapp")
|
||||||
|
|
||||||
|
cfg = IAMConfig.from_env()
|
||||||
|
assert cfg.server_url == "https://iam.example"
|
||||||
|
assert cfg.client_id == "cid"
|
||||||
|
assert cfg.client_secret == "csec"
|
||||||
|
assert cfg.organization == "acme"
|
||||||
|
assert cfg.application == "myapp"
|
||||||
|
|
||||||
|
def test_ignores_hanzo_iam_legacy(self, monkeypatch):
|
||||||
|
# Legacy aliases must NOT be honored.
|
||||||
|
monkeypatch.setenv("HANZO_IAM_CLIENT_ID", "legacy")
|
||||||
|
monkeypatch.setenv("HANZO_IAM_CLIENT_SECRET", "legacy-secret")
|
||||||
|
monkeypatch.setenv("HANZO_IAM_ENDPOINT", "https://legacy.example")
|
||||||
|
|
||||||
|
cfg = IAMConfig.from_env()
|
||||||
|
assert cfg.client_id == ""
|
||||||
|
assert cfg.client_secret == ""
|
||||||
|
assert cfg.server_url == ""
|
||||||
|
|
||||||
|
def test_ignores_org_prefixed_legacy(self, monkeypatch):
|
||||||
|
# {ORG}_IAM_* aliases must NOT be honored.
|
||||||
|
monkeypatch.setenv("LUX_IAM_CLIENT_ID", "leak")
|
||||||
|
monkeypatch.setenv("ZOO_IAM_CLIENT_SECRET", "leak2")
|
||||||
|
|
||||||
|
cfg = IAMConfig.from_env()
|
||||||
|
assert cfg.client_id == ""
|
||||||
|
assert cfg.client_secret == ""
|
||||||
|
|
||||||
|
def test_defaults_when_empty(self, monkeypatch):
|
||||||
|
cfg = IAMConfig.from_env()
|
||||||
|
assert cfg.server_url == ""
|
||||||
|
assert cfg.client_id == ""
|
||||||
|
assert cfg.organization == "hanzo"
|
||||||
|
assert cfg.application == "app"
|
||||||
|
|
||||||
|
|
||||||
|
class TestClientConfigFromEnv:
|
||||||
|
"""IAMClient._config_from_env reads only IAM_* vars."""
|
||||||
|
|
||||||
|
def test_reads_iam_vars(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("IAM_ENDPOINT", "https://iam.example")
|
||||||
|
monkeypatch.setenv("IAM_CLIENT_ID", "cid")
|
||||||
|
monkeypatch.setenv("IAM_CLIENT_SECRET", "csec")
|
||||||
|
monkeypatch.setenv("IAM_ORG", "acme")
|
||||||
|
monkeypatch.setenv("IAM_APP", "myapp")
|
||||||
|
monkeypatch.setenv("IAM_CERT", "")
|
||||||
|
|
||||||
|
cfg = IAMClient._config_from_env(Organization.HANZO)
|
||||||
|
assert cfg.server_url == "https://iam.example"
|
||||||
|
assert cfg.client_id == "cid"
|
||||||
|
assert cfg.client_secret == "csec"
|
||||||
|
assert cfg.organization == "acme"
|
||||||
|
assert cfg.application == "myapp"
|
||||||
|
|
||||||
|
def test_endpoint_defaults_to_org_url(self, monkeypatch):
|
||||||
|
cfg = IAMClient._config_from_env(Organization.ZOO)
|
||||||
|
assert cfg.server_url == "https://zoo.id"
|
||||||
|
assert cfg.organization == "zoo"
|
||||||
|
|
||||||
|
def test_ignores_hanzo_iam_legacy(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("HANZO_IAM_CLIENT_ID", "legacy")
|
||||||
|
monkeypatch.setenv("HANZO_IAM_URL", "https://legacy.example")
|
||||||
|
|
||||||
|
cfg = IAMClient._config_from_env(Organization.HANZO)
|
||||||
|
assert cfg.client_id == ""
|
||||||
|
assert cfg.server_url == "https://hanzo.id" # org default, NOT legacy URL
|
||||||
|
|
||||||
|
def test_ignores_org_prefixed_legacy(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("LUX_IAM_CLIENT_ID", "leak")
|
||||||
|
monkeypatch.setenv("ZOO_IAM_CLIENT_ID", "leak2")
|
||||||
|
|
||||||
|
cfg = IAMClient._config_from_env(Organization.LUX)
|
||||||
|
assert cfg.client_id == ""
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
@@ -43,7 +43,7 @@ class TestConfigure:
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# Ensure env var is not set
|
# Ensure env var is not set
|
||||||
os.environ.pop("HANZO_IAM_CLIENT_ID", None)
|
os.environ.pop("IAM_CLIENT_ID", None)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="client_id required"):
|
with pytest.raises(ValueError, match="client_id required"):
|
||||||
configure(client_id=None)
|
configure(client_id=None)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ classifiers = [
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hanzo-tools-core>=0.1.0",
|
"hanzo-tools-core>=0.1.0",
|
||||||
"hanzo-iam>=0.1.0",
|
"hanzo-iam>=1.30.0",
|
||||||
"httpx>=0.27.0",
|
"httpx>=0.27.0",
|
||||||
"pyjwt>=2.8.0",
|
"pyjwt>=2.8.0",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ classifiers = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hanzo-tools-core>=0.1.0",
|
"hanzo-tools-core>=0.1.0",
|
||||||
"hanzo-tools-auth>=0.1.0",
|
"hanzo-tools-auth>=0.1.0",
|
||||||
"hanzo-iam>=0.1.0",
|
"hanzo-iam>=1.30.0",
|
||||||
"httpx>=0.27.0",
|
"httpx>=0.27.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user