kortana-dev-sdk 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: kortana-dev-sdk
3
+ Version: 1.0.0
4
+ Summary: Official Kortana SDK for Python
5
+ License: MIT
6
+ Author: Kortana Labs
7
+ Author-email: engineering@kortana.io
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: anyio (>=4.0.0,<5.0.0)
18
+ Requires-Dist: cryptography (>=42.0.0,<43.0.0)
19
+ Requires-Dist: httpx (>=0.27.0,<0.28.0)
20
+ Requires-Dist: pydantic (>=2.5.0,<3.0.0)
21
+ Requires-Dist: pydantic-settings (>=2.1.0,<3.0.0)
22
+ Project-URL: Homepage, https://docs.kortana.io/sdk/python
23
+ Project-URL: Repository, https://github.com/kortana-labs/sdk-python
24
+ Description-Content-Type: text/markdown
25
+
26
+ # kortana-dev-sdk — Official Kortana Python SDK
27
+
28
+ [![PyPI](https://img.shields.io/pypi/v/kortana-dev-sdk)](https://pypi.org/project/kortana-dev-sdk/)
29
+ [![Python](https://img.shields.io/pypi/pyversions/kortana-dev-sdk)](https://pypi.org/project/kortana-dev-sdk/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
31
+
32
+ The official Python SDK for the [Kortana](https://kortana.io) blockchain payments platform.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install kortana-dev-sdk
38
+ # or
39
+ poetry add kortana-dev-sdk
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ import asyncio
46
+ import os
47
+ from kortana import kortana
48
+
49
+ async def main():
50
+ async with kortana(
51
+ api_key=os.environ["KORTANA_API_KEY"],
52
+ environment="mainnet",
53
+ ) as client:
54
+ # Check network status
55
+ status = await client.blockchain.get_network_status()
56
+ print(f"Block: {status.block_number}")
57
+
58
+ # Get wallet balance
59
+ balance = await client.wallets.get_balance()
60
+ print(f"Balance: {balance.available} DNR")
61
+
62
+ # Create a payment link
63
+ link = await client.payments.create_link(
64
+ amount=1_000_000_000_000_000_000, # 1 DNR in wei
65
+ currency="DNR",
66
+ description="Invoice #1001",
67
+ )
68
+ print(f"Payment URL: {link.url}")
69
+
70
+ asyncio.run(main())
71
+ ```
72
+
73
+ ## Webhook Verification
74
+
75
+ ```python
76
+ from kortana import kortana
77
+
78
+ client = kortana(api_key="kt_live_...", webhook_secret="whsec_...")
79
+
80
+ @app.post("/webhooks")
81
+ async def handle_webhook(request: Request):
82
+ payload = await request.body()
83
+ signature = request.headers.get("X-Kortana-Signature", "")
84
+ event = client.validate_webhook(payload.decode(), signature)
85
+ if event["type"] == "transfer.completed":
86
+ print("Transfer done!", event["data"])
87
+ return {"received": True}
88
+ ```
89
+
90
+ ## Modules
91
+
92
+ | Module | Description |
93
+ |---|---|
94
+ | `client.blockchain` | Read-only blockchain data (blocks, txs, gas) |
95
+ | `client.contracts` | Deploy and interact with smart contracts |
96
+ | `client.wallets` | HD wallet management and transfers |
97
+ | `client.payments` | Payment intents, invoices, subscriptions |
98
+ | `client.banking` | Customer and bank account management |
99
+ | `client.cards` | Virtual and physical card issuance |
100
+ | `client.compliance` | KYC, KYB, and AML screening |
101
+ | `client.settlement` | Ledger settlement and treasury |
102
+ | `client.merchants` | Merchant onboarding and management |
103
+
104
+ ## Requirements
105
+
106
+ - Python 3.9+
107
+ - `httpx >= 0.27`
108
+ - `pydantic >= 2.5`
109
+
110
+ ## License
111
+
112
+ MIT © Kortana Labs
113
+
@@ -0,0 +1,87 @@
1
+ # kortana-dev-sdk — Official Kortana Python SDK
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/kortana-dev-sdk)](https://pypi.org/project/kortana-dev-sdk/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/kortana-dev-sdk)](https://pypi.org/project/kortana-dev-sdk/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ The official Python SDK for the [Kortana](https://kortana.io) blockchain payments platform.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install kortana-dev-sdk
13
+ # or
14
+ poetry add kortana-dev-sdk
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ import asyncio
21
+ import os
22
+ from kortana import kortana
23
+
24
+ async def main():
25
+ async with kortana(
26
+ api_key=os.environ["KORTANA_API_KEY"],
27
+ environment="mainnet",
28
+ ) as client:
29
+ # Check network status
30
+ status = await client.blockchain.get_network_status()
31
+ print(f"Block: {status.block_number}")
32
+
33
+ # Get wallet balance
34
+ balance = await client.wallets.get_balance()
35
+ print(f"Balance: {balance.available} DNR")
36
+
37
+ # Create a payment link
38
+ link = await client.payments.create_link(
39
+ amount=1_000_000_000_000_000_000, # 1 DNR in wei
40
+ currency="DNR",
41
+ description="Invoice #1001",
42
+ )
43
+ print(f"Payment URL: {link.url}")
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ ## Webhook Verification
49
+
50
+ ```python
51
+ from kortana import kortana
52
+
53
+ client = kortana(api_key="kt_live_...", webhook_secret="whsec_...")
54
+
55
+ @app.post("/webhooks")
56
+ async def handle_webhook(request: Request):
57
+ payload = await request.body()
58
+ signature = request.headers.get("X-Kortana-Signature", "")
59
+ event = client.validate_webhook(payload.decode(), signature)
60
+ if event["type"] == "transfer.completed":
61
+ print("Transfer done!", event["data"])
62
+ return {"received": True}
63
+ ```
64
+
65
+ ## Modules
66
+
67
+ | Module | Description |
68
+ |---|---|
69
+ | `client.blockchain` | Read-only blockchain data (blocks, txs, gas) |
70
+ | `client.contracts` | Deploy and interact with smart contracts |
71
+ | `client.wallets` | HD wallet management and transfers |
72
+ | `client.payments` | Payment intents, invoices, subscriptions |
73
+ | `client.banking` | Customer and bank account management |
74
+ | `client.cards` | Virtual and physical card issuance |
75
+ | `client.compliance` | KYC, KYB, and AML screening |
76
+ | `client.settlement` | Ledger settlement and treasury |
77
+ | `client.merchants` | Merchant onboarding and management |
78
+
79
+ ## Requirements
80
+
81
+ - Python 3.9+
82
+ - `httpx >= 0.27`
83
+ - `pydantic >= 2.5`
84
+
85
+ ## License
86
+
87
+ MIT © Kortana Labs
@@ -0,0 +1,67 @@
1
+ """
2
+ kortana-dev-sdk — Official Kortana Python SDK.
3
+
4
+ Usage:
5
+ from kortana import kortana
6
+
7
+ async with kortana(api_key="kt_live_...", environment="mainnet") as client:
8
+ balance = await client.wallets.get_balance()
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from .client import AsyncKortanaClient
14
+ from .config import KortanaConfig
15
+ from .exceptions import (
16
+ KortanaError,
17
+ KortanaConfigError,
18
+ KortanaAuthError,
19
+ KortanaNetworkError,
20
+ KortanaRateLimitError,
21
+ KortanaValidationError,
22
+ KortanaNotFoundError,
23
+ KortanaConflictError,
24
+ KortanaWebhookSignatureError,
25
+ KortanaServerError,
26
+ )
27
+
28
+ __version__ = "1.0.0"
29
+ __all__ = [
30
+ "kortana",
31
+ "AsyncKortanaClient",
32
+ "KortanaConfig",
33
+ "KortanaError",
34
+ "KortanaConfigError",
35
+ "KortanaAuthError",
36
+ "KortanaNetworkError",
37
+ "KortanaRateLimitError",
38
+ "KortanaValidationError",
39
+ "KortanaNotFoundError",
40
+ "KortanaConflictError",
41
+ "KortanaWebhookSignatureError",
42
+ "KortanaServerError",
43
+ ]
44
+
45
+
46
+ def kortana(
47
+ api_key: str,
48
+ environment: str = "mainnet",
49
+ **kwargs: object,
50
+ ) -> AsyncKortanaClient:
51
+ """
52
+ Factory function to create a Kortana client.
53
+
54
+ Args:
55
+ api_key: Your Kortana API key (kt_live_... or kt_test_...).
56
+ environment: 'mainnet' or 'testnet'. Defaults to 'mainnet'.
57
+ **kwargs: Additional config overrides (timeout, max_retries, webhook_secret, etc.).
58
+
59
+ Returns:
60
+ AsyncKortanaClient that supports use as an async context manager.
61
+
62
+ Example:
63
+ async with kortana(api_key=os.environ["KORTANA_API_KEY"]) as client:
64
+ balance = await client.wallets.get_balance()
65
+ """
66
+ config = KortanaConfig(api_key=api_key, environment=environment, **kwargs) # type: ignore[arg-type]
67
+ return AsyncKortanaClient(config)
@@ -0,0 +1 @@
1
+ """Auth sub-package for Kortana SDK."""
@@ -0,0 +1,65 @@
1
+ """
2
+ Authenticator — handles API key pass-through and JWT token auto-refresh.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import time
7
+ from typing import Optional
8
+
9
+ import httpx
10
+
11
+ from kortana.config import KortanaConfig
12
+ from kortana.exceptions import KortanaAuthError
13
+
14
+
15
+ class Authenticator:
16
+ """
17
+ Manages authentication tokens for the Kortana API.
18
+
19
+ - API keys (kt_live_... / kt_test_...) are passed through directly.
20
+ - Other credentials trigger JWT token acquisition with auto-refresh
21
+ 60 seconds before expiry.
22
+ """
23
+
24
+ _TOKEN_TTL_SECONDS = 900 # 15 minutes
25
+ _REFRESH_BUFFER_SECONDS = 60
26
+
27
+ def __init__(self, config: KortanaConfig) -> None:
28
+ self._config = config
29
+ self._token: Optional[str] = None
30
+ self._token_expiry: float = 0.0
31
+
32
+ async def get_token(self) -> str:
33
+ """Return a valid authentication token."""
34
+ if self._config.api_key.startswith("kt_"):
35
+ # Direct API key — pass through as Bearer token
36
+ return self._config.api_key
37
+
38
+ # JWT mode — refresh if expired or expiring soon
39
+ if self._token and time.monotonic() < self._token_expiry - self._REFRESH_BUFFER_SECONDS:
40
+ return self._token
41
+
42
+ return await self._refresh_token()
43
+
44
+ async def _refresh_token(self) -> str:
45
+ """Obtain a new JWT from the auth endpoint."""
46
+ auth_base = self._config.get_base_url().replace("/v1", "")
47
+ url = f"{auth_base}/auth/token"
48
+
49
+ async with httpx.AsyncClient(timeout=self._config.connect_timeout) as client:
50
+ response = await client.post(
51
+ url,
52
+ headers={
53
+ "Authorization": f"Bearer {self._config.api_key}",
54
+ "Content-Type": "application/json",
55
+ },
56
+ )
57
+
58
+ if not response.is_success:
59
+ raise KortanaAuthError("Failed to authenticate with Kortana API")
60
+
61
+ data = response.json()
62
+ token: str = data["data"]["token"]
63
+ self._token = token
64
+ self._token_expiry = time.monotonic() + self._TOKEN_TTL_SECONDS
65
+ return self._token
@@ -0,0 +1,58 @@
1
+ """
2
+ Core Async Kortana client.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import Any
7
+
8
+ from kortana.auth.authenticator import Authenticator
9
+ from kortana.config import KortanaConfig
10
+ from kortana.http.client import AsyncHttpClient
11
+ from kortana.modules.banking import BankingModule
12
+ from kortana.modules.blockchain import BlockchainModule
13
+ from kortana.modules.cards import CardsModule
14
+ from kortana.modules.compliance import ComplianceModule
15
+ from kortana.modules.contracts import ContractsModule
16
+ from kortana.modules.merchants import MerchantsModule
17
+ from kortana.modules.payments import PaymentsModule
18
+ from kortana.modules.settlement import SettlementModule
19
+ from kortana.modules.wallets import WalletsModule
20
+ from kortana.webhooks.verifier import Webhook
21
+
22
+
23
+ class AsyncKortanaClient:
24
+ """
25
+ Official asynchronous client for the Kortana blockchain platform.
26
+ """
27
+
28
+ def __init__(self, config: KortanaConfig) -> None:
29
+ self._config = config
30
+ self._auth = Authenticator(config)
31
+ self._http = AsyncHttpClient(config, self._auth)
32
+ self._webhook_verifier = Webhook(config.webhook_secret)
33
+
34
+ # Initialize all domain modules
35
+ self.blockchain = BlockchainModule(self._http)
36
+ self.wallets = WalletsModule(self._http)
37
+ self.contracts = ContractsModule(self._http)
38
+ self.payments = PaymentsModule(self._http)
39
+ self.merchants = MerchantsModule(self._http)
40
+ self.banking = BankingModule(self._http)
41
+ self.cards = CardsModule(self._http)
42
+ self.compliance = ComplianceModule(self._http)
43
+ self.settlement = SettlementModule(self._http)
44
+
45
+ def validate_webhook(self, payload: str, signature: str) -> dict[str, Any]:
46
+ """Validate webhook payload and signature."""
47
+ return self._webhook_verifier.verify(payload, signature)
48
+
49
+ async def get_health(self) -> dict[str, Any]:
50
+ """Get the health status of the API backend."""
51
+ return await self._http.get("/health") # type: ignore[no-any-return]
52
+
53
+ async def __aenter__(self) -> AsyncKortanaClient:
54
+ await self._http.__aenter__()
55
+ return self
56
+
57
+ async def __aexit__(self, *args: Any) -> None:
58
+ await self._http.__aexit__(*args)
@@ -0,0 +1,75 @@
1
+ """
2
+ Kortana SDK configuration using Pydantic settings.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import Optional
7
+ from pydantic import field_validator, model_validator
8
+ from pydantic_settings import BaseSettings
9
+
10
+ BASE_URLS: dict[str, str] = {
11
+ "mainnet": "https://api.oneworld.kortana.network/api/v1",
12
+ "testnet": "https://api.oneworld.kortana.network/api/v1",
13
+ }
14
+
15
+
16
+ class KortanaConfig(BaseSettings):
17
+ """
18
+ Kortana SDK configuration.
19
+
20
+ All fields can be overridden via environment variables prefixed with KORTANA_.
21
+ Example: KORTANA_API_KEY, KORTANA_ENVIRONMENT, KORTANA_TIMEOUT
22
+ """
23
+
24
+ api_key: str
25
+ environment: str = "mainnet"
26
+ base_url: Optional[str] = None
27
+ timeout: float = 30.0
28
+ connect_timeout: float = 5.0
29
+ max_retries: int = 3
30
+ retry_delay: float = 1.0
31
+ max_retry_delay: float = 32.0
32
+ log_level: str = "info"
33
+ webhook_secret: Optional[str] = None
34
+ user_agent: Optional[str] = None
35
+ telemetry: bool = True
36
+
37
+ model_config = {"env_prefix": "KORTANA_", "populate_by_name": True} # type: ignore[assignment]
38
+
39
+ @field_validator("environment")
40
+ @classmethod
41
+ def validate_environment(cls, v: str) -> str:
42
+ if v not in ("mainnet", "testnet"):
43
+ raise ValueError(f"environment must be 'mainnet' or 'testnet', got {v!r}")
44
+ return v
45
+
46
+ @field_validator("api_key")
47
+ @classmethod
48
+ def validate_api_key(cls, v: str) -> str:
49
+ if not v or not v.strip():
50
+ raise ValueError("api_key must not be empty")
51
+ return v.strip()
52
+
53
+ @field_validator("timeout")
54
+ @classmethod
55
+ def validate_timeout(cls, v: float) -> float:
56
+ if v <= 0:
57
+ raise ValueError("timeout must be positive")
58
+ return v
59
+
60
+ @field_validator("max_retries")
61
+ @classmethod
62
+ def validate_max_retries(cls, v: int) -> int:
63
+ if v < 0:
64
+ raise ValueError("max_retries must be non-negative")
65
+ return v
66
+
67
+ def get_base_url(self) -> str:
68
+ """Return the resolved base URL."""
69
+ if self.base_url:
70
+ return self.base_url.rstrip("/")
71
+ return BASE_URLS[self.environment]
72
+
73
+ def get_user_agent(self) -> str:
74
+ """Return the User-Agent header value."""
75
+ return self.user_agent or "kortana-sdk-python/1.0.0"
@@ -0,0 +1,183 @@
1
+ """
2
+ Kortana SDK exception hierarchy.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Optional
7
+
8
+
9
+ class KortanaError(Exception):
10
+ """Base exception for all Kortana SDK errors."""
11
+
12
+ def __init__(
13
+ self,
14
+ message: str,
15
+ code: str = "UNKNOWN_ERROR",
16
+ request_id: Optional[str] = None,
17
+ docs_url: Optional[str] = None,
18
+ details: Optional[dict[str, Any]] = None,
19
+ ) -> None:
20
+ super().__init__(message)
21
+ self.message = message
22
+ self.code = code
23
+ self.request_id = request_id
24
+ self.docs_url = docs_url
25
+ self.details = details or {}
26
+
27
+ def __repr__(self) -> str:
28
+ return f"{self.__class__.__name__}(code={self.code!r}, message={self.message!r})"
29
+
30
+
31
+ class KortanaConfigError(KortanaError):
32
+ """Raised when the SDK is misconfigured."""
33
+
34
+ def __init__(self, message: str) -> None:
35
+ super().__init__(
36
+ message,
37
+ code="CONFIG_ERROR",
38
+ docs_url="https://docs.kortana.io/errors/CONFIG_ERROR",
39
+ )
40
+
41
+
42
+ class KortanaAuthError(KortanaError):
43
+ """Raised on authentication failures (401)."""
44
+
45
+ def __init__(self, message: str, request_id: Optional[str] = None) -> None:
46
+ super().__init__(
47
+ message,
48
+ code="AUTH_ERROR",
49
+ request_id=request_id,
50
+ docs_url="https://docs.kortana.io/errors/AUTH_ERROR",
51
+ )
52
+
53
+
54
+ class KortanaNetworkError(KortanaError):
55
+ """Raised on network-level failures (timeouts, connection errors)."""
56
+
57
+ def __init__(
58
+ self,
59
+ message: str,
60
+ cause: Optional[Exception] = None,
61
+ request_id: Optional[str] = None,
62
+ ) -> None:
63
+ super().__init__(
64
+ message,
65
+ code="NETWORK_ERROR",
66
+ request_id=request_id,
67
+ docs_url="https://docs.kortana.io/errors/NETWORK_ERROR",
68
+ )
69
+ self.cause = cause
70
+
71
+
72
+ class KortanaRateLimitError(KortanaError):
73
+ """Raised when the API rate limit is exceeded (429)."""
74
+
75
+ def __init__(
76
+ self,
77
+ message: str,
78
+ retry_after: int = 60,
79
+ request_id: Optional[str] = None,
80
+ ) -> None:
81
+ super().__init__(
82
+ message,
83
+ code="RATE_LIMIT_ERROR",
84
+ request_id=request_id,
85
+ docs_url="https://docs.kortana.io/errors/RATE_LIMIT_ERROR",
86
+ )
87
+ self.retry_after = retry_after
88
+
89
+
90
+ class KortanaValidationError(KortanaError):
91
+ """Raised when request validation fails (400)."""
92
+
93
+ def __init__(
94
+ self,
95
+ message: str,
96
+ fields: Optional[list[dict[str, str]]] = None,
97
+ request_id: Optional[str] = None,
98
+ ) -> None:
99
+ super().__init__(
100
+ message,
101
+ code="VALIDATION_ERROR",
102
+ request_id=request_id,
103
+ docs_url="https://docs.kortana.io/errors/VALIDATION_ERROR",
104
+ )
105
+ self.fields = fields or []
106
+
107
+
108
+ class KortanaNotFoundError(KortanaError):
109
+ """Raised when a resource is not found (404)."""
110
+
111
+ def __init__(self, message: str, request_id: Optional[str] = None) -> None:
112
+ super().__init__(
113
+ message,
114
+ code="NOT_FOUND_ERROR",
115
+ request_id=request_id,
116
+ docs_url="https://docs.kortana.io/errors/NOT_FOUND_ERROR",
117
+ )
118
+
119
+
120
+ class KortanaConflictError(KortanaError):
121
+ """Raised on resource conflicts (409)."""
122
+
123
+ def __init__(self, message: str, request_id: Optional[str] = None) -> None:
124
+ super().__init__(
125
+ message,
126
+ code="CONFLICT_ERROR",
127
+ request_id=request_id,
128
+ docs_url="https://docs.kortana.io/errors/CONFLICT_ERROR",
129
+ )
130
+
131
+
132
+ class KortanaWebhookSignatureError(KortanaError):
133
+ """Raised when webhook signature verification fails."""
134
+
135
+ def __init__(self, message: str) -> None:
136
+ super().__init__(
137
+ message,
138
+ code="WEBHOOK_SIGNATURE_ERROR",
139
+ docs_url="https://docs.kortana.io/errors/WEBHOOK_SIGNATURE_ERROR",
140
+ )
141
+
142
+
143
+ class KortanaServerError(KortanaError):
144
+ """Raised on server-side errors (5xx)."""
145
+
146
+ def __init__(self, message: str, request_id: Optional[str] = None) -> None:
147
+ super().__init__(
148
+ message,
149
+ code="SERVER_ERROR",
150
+ request_id=request_id,
151
+ docs_url="https://docs.kortana.io/errors/SERVER_ERROR",
152
+ )
153
+
154
+
155
+ def raise_for_response(
156
+ status_code: int,
157
+ error_body: dict[str, Any],
158
+ headers: dict[str, str],
159
+ ) -> None:
160
+ """Map HTTP status codes and error bodies to specific KortanaError subclasses."""
161
+ error = error_body.get("error", {})
162
+ message = error.get("message", "An unknown error occurred")
163
+ request_id = error.get("request_id")
164
+ fields = error.get("fields")
165
+
166
+ if status_code == 400:
167
+ raise KortanaValidationError(message, fields=fields, request_id=request_id)
168
+ if status_code == 401:
169
+ raise KortanaAuthError(message, request_id=request_id)
170
+ if status_code == 404:
171
+ raise KortanaNotFoundError(message, request_id=request_id)
172
+ if status_code == 409:
173
+ raise KortanaConflictError(message, request_id=request_id)
174
+ if status_code == 429:
175
+ retry_after = int(headers.get("retry-after", "60"))
176
+ raise KortanaRateLimitError(message, retry_after=retry_after, request_id=request_id)
177
+ if status_code >= 500:
178
+ raise KortanaServerError(message, request_id=request_id)
179
+
180
+ code = error.get("code", "UNKNOWN_ERROR")
181
+ docs_url = error.get("docs_url")
182
+ details = error.get("details")
183
+ raise KortanaError(message, code=code, request_id=request_id, docs_url=docs_url, details=details)
@@ -0,0 +1 @@
1
+ """HTTP sub-package for Kortana SDK."""