binalyze-air-sdk 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
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.
- binalyze_air/__init__.py +77 -77
- binalyze_air/apis/__init__.py +67 -27
- binalyze_air/apis/acquisitions.py +107 -0
- binalyze_air/apis/api_tokens.py +49 -0
- binalyze_air/apis/assets.py +161 -0
- binalyze_air/apis/audit_logs.py +26 -0
- binalyze_air/apis/{authentication.py → auth.py} +29 -27
- binalyze_air/apis/auto_asset_tags.py +79 -75
- binalyze_air/apis/backup.py +177 -0
- binalyze_air/apis/baseline.py +46 -0
- binalyze_air/apis/cases.py +225 -0
- binalyze_air/apis/cloud_forensics.py +116 -0
- binalyze_air/apis/event_subscription.py +96 -96
- binalyze_air/apis/evidence.py +249 -53
- binalyze_air/apis/interact.py +153 -36
- binalyze_air/apis/investigation_hub.py +234 -0
- binalyze_air/apis/license.py +104 -0
- binalyze_air/apis/logger.py +83 -0
- binalyze_air/apis/multipart_upload.py +201 -0
- binalyze_air/apis/notifications.py +115 -0
- binalyze_air/apis/organizations.py +267 -0
- binalyze_air/apis/params.py +44 -39
- binalyze_air/apis/policies.py +186 -0
- binalyze_air/apis/preset_filters.py +79 -0
- binalyze_air/apis/recent_activities.py +71 -0
- binalyze_air/apis/relay_server.py +104 -0
- binalyze_air/apis/settings.py +395 -27
- binalyze_air/apis/tasks.py +80 -0
- binalyze_air/apis/triage.py +197 -0
- binalyze_air/apis/user_management.py +183 -74
- binalyze_air/apis/webhook_executions.py +50 -0
- binalyze_air/apis/webhooks.py +322 -230
- binalyze_air/base.py +207 -133
- binalyze_air/client.py +217 -1337
- binalyze_air/commands/__init__.py +175 -145
- binalyze_air/commands/acquisitions.py +661 -387
- binalyze_air/commands/api_tokens.py +55 -0
- binalyze_air/commands/assets.py +324 -362
- binalyze_air/commands/{authentication.py → auth.py} +36 -36
- binalyze_air/commands/auto_asset_tags.py +230 -230
- binalyze_air/commands/backup.py +47 -0
- binalyze_air/commands/baseline.py +32 -396
- binalyze_air/commands/cases.py +609 -602
- binalyze_air/commands/cloud_forensics.py +88 -0
- binalyze_air/commands/event_subscription.py +101 -101
- binalyze_air/commands/evidences.py +918 -988
- binalyze_air/commands/interact.py +172 -58
- binalyze_air/commands/investigation_hub.py +315 -0
- binalyze_air/commands/license.py +183 -0
- binalyze_air/commands/logger.py +126 -0
- binalyze_air/commands/multipart_upload.py +363 -0
- binalyze_air/commands/notifications.py +45 -0
- binalyze_air/commands/organizations.py +200 -221
- binalyze_air/commands/policies.py +175 -203
- binalyze_air/commands/preset_filters.py +55 -0
- binalyze_air/commands/recent_activities.py +32 -0
- binalyze_air/commands/relay_server.py +144 -0
- binalyze_air/commands/settings.py +431 -29
- binalyze_air/commands/tasks.py +95 -56
- binalyze_air/commands/triage.py +224 -360
- binalyze_air/commands/user_management.py +351 -126
- binalyze_air/commands/webhook_executions.py +77 -0
- binalyze_air/config.py +244 -244
- binalyze_air/exceptions.py +49 -49
- binalyze_air/http_client.py +426 -305
- binalyze_air/models/__init__.py +287 -285
- binalyze_air/models/acquisitions.py +365 -250
- binalyze_air/models/api_tokens.py +73 -0
- binalyze_air/models/assets.py +438 -438
- binalyze_air/models/audit.py +247 -272
- binalyze_air/models/audit_logs.py +14 -0
- binalyze_air/models/{authentication.py → auth.py} +69 -69
- binalyze_air/models/auto_asset_tags.py +227 -116
- binalyze_air/models/backup.py +138 -0
- binalyze_air/models/baseline.py +231 -231
- binalyze_air/models/cases.py +275 -275
- binalyze_air/models/cloud_forensics.py +145 -0
- binalyze_air/models/event_subscription.py +170 -171
- binalyze_air/models/evidence.py +65 -65
- binalyze_air/models/evidences.py +367 -348
- binalyze_air/models/interact.py +266 -135
- binalyze_air/models/investigation_hub.py +265 -0
- binalyze_air/models/license.py +150 -0
- binalyze_air/models/logger.py +83 -0
- binalyze_air/models/multipart_upload.py +352 -0
- binalyze_air/models/notifications.py +138 -0
- binalyze_air/models/organizations.py +293 -293
- binalyze_air/models/params.py +153 -127
- binalyze_air/models/policies.py +260 -249
- binalyze_air/models/preset_filters.py +79 -0
- binalyze_air/models/recent_activities.py +70 -0
- binalyze_air/models/relay_server.py +121 -0
- binalyze_air/models/settings.py +538 -84
- binalyze_air/models/tasks.py +215 -149
- binalyze_air/models/triage.py +141 -142
- binalyze_air/models/user_management.py +200 -97
- binalyze_air/models/webhook_executions.py +33 -0
- binalyze_air/queries/__init__.py +121 -133
- binalyze_air/queries/acquisitions.py +155 -155
- binalyze_air/queries/api_tokens.py +46 -0
- binalyze_air/queries/assets.py +186 -105
- binalyze_air/queries/audit.py +400 -416
- binalyze_air/queries/{authentication.py → auth.py} +55 -55
- binalyze_air/queries/auto_asset_tags.py +59 -59
- binalyze_air/queries/backup.py +66 -0
- binalyze_air/queries/baseline.py +21 -185
- binalyze_air/queries/cases.py +292 -292
- binalyze_air/queries/cloud_forensics.py +137 -0
- binalyze_air/queries/event_subscription.py +54 -54
- binalyze_air/queries/evidence.py +139 -139
- binalyze_air/queries/evidences.py +279 -279
- binalyze_air/queries/interact.py +140 -28
- binalyze_air/queries/investigation_hub.py +329 -0
- binalyze_air/queries/license.py +85 -0
- binalyze_air/queries/logger.py +58 -0
- binalyze_air/queries/multipart_upload.py +180 -0
- binalyze_air/queries/notifications.py +71 -0
- binalyze_air/queries/organizations.py +222 -222
- binalyze_air/queries/params.py +154 -115
- binalyze_air/queries/policies.py +149 -149
- binalyze_air/queries/preset_filters.py +60 -0
- binalyze_air/queries/recent_activities.py +44 -0
- binalyze_air/queries/relay_server.py +42 -0
- binalyze_air/queries/settings.py +533 -20
- binalyze_air/queries/tasks.py +125 -81
- binalyze_air/queries/triage.py +230 -230
- binalyze_air/queries/user_management.py +193 -83
- binalyze_air/queries/webhook_executions.py +39 -0
- binalyze_air_sdk-1.0.3.dist-info/METADATA +752 -0
- binalyze_air_sdk-1.0.3.dist-info/RECORD +132 -0
- {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/WHEEL +1 -1
- binalyze_air/apis/endpoints.py +0 -22
- binalyze_air/apis/evidences.py +0 -216
- binalyze_air/apis/users.py +0 -68
- binalyze_air/commands/users.py +0 -101
- binalyze_air/models/endpoints.py +0 -76
- binalyze_air/models/users.py +0 -82
- binalyze_air/queries/endpoints.py +0 -25
- binalyze_air/queries/users.py +0 -69
- binalyze_air_sdk-1.0.1.dist-info/METADATA +0 -635
- binalyze_air_sdk-1.0.1.dist-info/RECORD +0 -82
- {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
binalyze_air/config.py
CHANGED
@@ -1,245 +1,245 @@
|
|
1
|
-
"""
|
2
|
-
Configuration management for the Binalyze AIR SDK.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import os
|
6
|
-
import json
|
7
|
-
from typing import Optional, Dict, Any, List
|
8
|
-
from pydantic import BaseModel, Field, field_validator
|
9
|
-
|
10
|
-
|
11
|
-
class PaginationConfig(BaseModel):
|
12
|
-
"""Pagination configuration."""
|
13
|
-
default_page_size: int = Field(default=10, description="Default page size")
|
14
|
-
max_page_size: int = Field(default=100, description="Maximum page size")
|
15
|
-
default_sort_by: str = Field(default="createdAt", description="Default sort field")
|
16
|
-
default_sort_type: str = Field(default="ASC", description="Default sort type")
|
17
|
-
|
18
|
-
|
19
|
-
class EndpointConfig(BaseModel):
|
20
|
-
"""API endpoint configuration."""
|
21
|
-
acquisitions: Dict[str, str] = Field(default_factory=dict)
|
22
|
-
assets: Dict[str, str] = Field(default_factory=dict)
|
23
|
-
audit: Dict[str, str] = Field(default_factory=dict)
|
24
|
-
auth: Dict[str, str] = Field(default_factory=dict)
|
25
|
-
auto_asset_tags: Dict[str, str] = Field(default_factory=dict)
|
26
|
-
baseline: Dict[str, str] = Field(default_factory=dict)
|
27
|
-
cases: Dict[str, str] = Field(default_factory=dict)
|
28
|
-
policies: Dict[str, str] = Field(default_factory=dict)
|
29
|
-
tasks: Dict[str, str] = Field(default_factory=dict)
|
30
|
-
task_assignments: Dict[str, str] = Field(default_factory=dict)
|
31
|
-
triage_rules: Dict[str, str] = Field(default_factory=dict)
|
32
|
-
organizations: Dict[str, str] = Field(default_factory=dict)
|
33
|
-
users: Dict[str, str] = Field(default_factory=dict)
|
34
|
-
repositories: Dict[str, str] = Field(default_factory=dict)
|
35
|
-
|
36
|
-
|
37
|
-
class DefaultFiltersConfig(BaseModel):
|
38
|
-
"""Default filter configuration."""
|
39
|
-
organization_ids: List[int] = Field(default=[0])
|
40
|
-
all_organizations: bool = Field(default=True)
|
41
|
-
managed_status: List[str] = Field(default=["managed"])
|
42
|
-
online_status: List[str] = Field(default=["online"])
|
43
|
-
sort_type: str = Field(default="ASC")
|
44
|
-
|
45
|
-
|
46
|
-
class TaskDefaultsConfig(BaseModel):
|
47
|
-
"""Task default configuration."""
|
48
|
-
cpu_limit: int = Field(default=80)
|
49
|
-
enable_compression: bool = Field(default=True)
|
50
|
-
enable_encryption: bool = Field(default=False)
|
51
|
-
bandwidth_limit: int = Field(default=100000)
|
52
|
-
chunk_size: int = Field(default=1048576)
|
53
|
-
chunk_count: int = Field(default=0)
|
54
|
-
start_offset: int = Field(default=0)
|
55
|
-
|
56
|
-
|
57
|
-
class LoggingConfig(BaseModel):
|
58
|
-
"""Logging configuration."""
|
59
|
-
level: str = Field(default="INFO")
|
60
|
-
format: str = Field(default="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
61
|
-
file: str = Field(default="air_sdk.log")
|
62
|
-
max_file_size: str = Field(default="10MB")
|
63
|
-
backup_count: int = Field(default=5)
|
64
|
-
|
65
|
-
|
66
|
-
class CacheConfig(BaseModel):
|
67
|
-
"""Cache configuration."""
|
68
|
-
enabled: bool = Field(default=True)
|
69
|
-
ttl: int = Field(default=300)
|
70
|
-
max_size: int = Field(default=1000)
|
71
|
-
|
72
|
-
|
73
|
-
class RateLimitingConfig(BaseModel):
|
74
|
-
"""Rate limiting configuration."""
|
75
|
-
enabled: bool = Field(default=True)
|
76
|
-
requests_per_minute: int = Field(default=100)
|
77
|
-
burst_size: int = Field(default=10)
|
78
|
-
|
79
|
-
|
80
|
-
class AIRConfig(BaseModel):
|
81
|
-
"""Configuration for the AIR SDK."""
|
82
|
-
|
83
|
-
host: str = Field(..., description="AIR instance host URL")
|
84
|
-
api_token: str = Field(..., description="API token for authentication")
|
85
|
-
api_prefix: str = Field(default="api/public", description="API prefix path")
|
86
|
-
organization_id: int = Field(default=0, description="Default organization ID")
|
87
|
-
timeout: int = Field(default=30, description="Request timeout in seconds")
|
88
|
-
verify_ssl: bool = Field(default=True, description="Whether to verify SSL certificates")
|
89
|
-
retry_attempts: int = Field(default=3, description="Number of retry attempts for failed requests")
|
90
|
-
retry_delay: float = Field(default=1.0, description="Delay between retry attempts")
|
91
|
-
|
92
|
-
# Enhanced configuration sections
|
93
|
-
pagination: PaginationConfig = Field(default_factory=PaginationConfig)
|
94
|
-
endpoints: EndpointConfig = Field(default_factory=EndpointConfig)
|
95
|
-
default_filters: DefaultFiltersConfig = Field(default_factory=DefaultFiltersConfig)
|
96
|
-
task_defaults: TaskDefaultsConfig = Field(default_factory=TaskDefaultsConfig)
|
97
|
-
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
98
|
-
cache: CacheConfig = Field(default_factory=CacheConfig)
|
99
|
-
rate_limiting: RateLimitingConfig = Field(default_factory=RateLimitingConfig)
|
100
|
-
|
101
|
-
@field_validator("host")
|
102
|
-
@classmethod
|
103
|
-
def validate_host(cls, v):
|
104
|
-
"""Ensure host URL is properly formatted."""
|
105
|
-
if not v.startswith(("http://", "https://")):
|
106
|
-
raise ValueError("Host must start with http:// or https://")
|
107
|
-
return v.rstrip("/")
|
108
|
-
|
109
|
-
@field_validator("api_token")
|
110
|
-
@classmethod
|
111
|
-
def validate_api_token(cls, v):
|
112
|
-
"""Ensure API token is not empty."""
|
113
|
-
if not v or not v.strip():
|
114
|
-
raise ValueError("API token cannot be empty")
|
115
|
-
return v.strip()
|
116
|
-
|
117
|
-
@property
|
118
|
-
def base_url(self) -> str:
|
119
|
-
"""Get the full base URL for API requests."""
|
120
|
-
return f"{self.host}/{self.api_prefix}"
|
121
|
-
|
122
|
-
def get_endpoint(self, category: str, endpoint: str) -> str:
|
123
|
-
"""Get a specific endpoint URL."""
|
124
|
-
category_endpoints = getattr(self.endpoints, category, {})
|
125
|
-
if isinstance(category_endpoints, dict):
|
126
|
-
return category_endpoints.get(endpoint, "")
|
127
|
-
return ""
|
128
|
-
|
129
|
-
def get_full_endpoint_url(self, category: str, endpoint: str, **kwargs) -> str:
|
130
|
-
"""Get the full URL for an endpoint with substitutions."""
|
131
|
-
endpoint_path = self.get_endpoint(category, endpoint)
|
132
|
-
if not endpoint_path:
|
133
|
-
raise ValueError(f"Endpoint not found: {category}.{endpoint}")
|
134
|
-
|
135
|
-
# Substitute path parameters
|
136
|
-
full_path = endpoint_path.format(**kwargs)
|
137
|
-
return f"{self.base_url}{full_path}"
|
138
|
-
|
139
|
-
@classmethod
|
140
|
-
def from_environment(cls) -> "AIRConfig":
|
141
|
-
"""Create configuration from environment variables."""
|
142
|
-
config_data = {
|
143
|
-
"host": os.getenv("AIR_HOST", ""),
|
144
|
-
"api_token": os.getenv("AIR_API_TOKEN", ""),
|
145
|
-
"api_prefix": os.getenv("AIR_API_PREFIX", "api/public"),
|
146
|
-
"organization_id": int(os.getenv("AIR_ORGANIZATION_ID", "0")),
|
147
|
-
"timeout": int(os.getenv("AIR_TIMEOUT", "30")),
|
148
|
-
"verify_ssl": os.getenv("AIR_VERIFY_SSL", "true").lower() == "true",
|
149
|
-
"retry_attempts": int(os.getenv("AIR_RETRY_ATTEMPTS", "3")),
|
150
|
-
"retry_delay": float(os.getenv("AIR_RETRY_DELAY", "1.0")),
|
151
|
-
}
|
152
|
-
|
153
|
-
return cls(**config_data)
|
154
|
-
|
155
|
-
@classmethod
|
156
|
-
def from_file(cls, config_path: str = "config.json") -> "AIRConfig":
|
157
|
-
"""Create configuration from JSON file."""
|
158
|
-
try:
|
159
|
-
with open(config_path, "r") as f:
|
160
|
-
config_data = json.load(f)
|
161
|
-
return cls(**config_data)
|
162
|
-
except FileNotFoundError:
|
163
|
-
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
164
|
-
except json.JSONDecodeError as e:
|
165
|
-
raise ValueError(f"Invalid JSON in configuration file: {e}")
|
166
|
-
|
167
|
-
@classmethod
|
168
|
-
def create(
|
169
|
-
cls,
|
170
|
-
host: Optional[str] = None,
|
171
|
-
api_token: Optional[str] = None,
|
172
|
-
organization_id: Optional[int] = None,
|
173
|
-
config_file: Optional[str] = None,
|
174
|
-
**kwargs
|
175
|
-
) -> "AIRConfig":
|
176
|
-
"""Create configuration with precedence: params > env vars > config file."""
|
177
|
-
config_data = {}
|
178
|
-
|
179
|
-
# 1. Try config file first (lowest precedence)
|
180
|
-
if config_file and os.path.exists(config_file):
|
181
|
-
try:
|
182
|
-
with open(config_file, "r") as f:
|
183
|
-
config_data = json.load(f)
|
184
|
-
except (json.JSONDecodeError, IOError):
|
185
|
-
pass # Ignore file errors, will try other methods
|
186
|
-
elif os.path.exists("config.json"):
|
187
|
-
# Try default config.json
|
188
|
-
try:
|
189
|
-
with open("config.json", "r") as f:
|
190
|
-
config_data = json.load(f)
|
191
|
-
except (json.JSONDecodeError, IOError):
|
192
|
-
pass
|
193
|
-
|
194
|
-
# 2. Override with environment variables
|
195
|
-
env_config = {}
|
196
|
-
if os.getenv("AIR_HOST"):
|
197
|
-
env_config["host"] = os.getenv("AIR_HOST")
|
198
|
-
if os.getenv("AIR_API_TOKEN"):
|
199
|
-
env_config["api_token"] = os.getenv("AIR_API_TOKEN")
|
200
|
-
if os.getenv("AIR_API_PREFIX"):
|
201
|
-
env_config["api_prefix"] = os.getenv("AIR_API_PREFIX")
|
202
|
-
|
203
|
-
org_id_env = os.getenv("AIR_ORGANIZATION_ID")
|
204
|
-
if org_id_env:
|
205
|
-
env_config["organization_id"] = int(org_id_env)
|
206
|
-
|
207
|
-
timeout_env = os.getenv("AIR_TIMEOUT")
|
208
|
-
if timeout_env:
|
209
|
-
env_config["timeout"] = int(timeout_env)
|
210
|
-
|
211
|
-
verify_ssl_env = os.getenv("AIR_VERIFY_SSL")
|
212
|
-
if verify_ssl_env:
|
213
|
-
env_config["verify_ssl"] = verify_ssl_env.lower() == "true"
|
214
|
-
|
215
|
-
retry_attempts_env = os.getenv("AIR_RETRY_ATTEMPTS")
|
216
|
-
if retry_attempts_env:
|
217
|
-
env_config["retry_attempts"] = int(retry_attempts_env)
|
218
|
-
|
219
|
-
retry_delay_env = os.getenv("AIR_RETRY_DELAY")
|
220
|
-
if retry_delay_env:
|
221
|
-
env_config["retry_delay"] = float(retry_delay_env)
|
222
|
-
|
223
|
-
config_data.update(env_config)
|
224
|
-
|
225
|
-
# 3. Override with explicit parameters (highest precedence)
|
226
|
-
if host is not None:
|
227
|
-
config_data["host"] = host
|
228
|
-
if api_token is not None:
|
229
|
-
config_data["api_token"] = api_token
|
230
|
-
if organization_id is not None:
|
231
|
-
config_data["organization_id"] = organization_id
|
232
|
-
|
233
|
-
# Add any additional kwargs
|
234
|
-
config_data.update(kwargs)
|
235
|
-
|
236
|
-
return cls(**config_data)
|
237
|
-
|
238
|
-
def to_dict(self) -> Dict[str, Any]:
|
239
|
-
"""Convert configuration to dictionary."""
|
240
|
-
return self.model_dump()
|
241
|
-
|
242
|
-
def save_to_file(self, config_path: str = "config.json") -> None:
|
243
|
-
"""Save configuration to JSON file."""
|
244
|
-
with open(config_path, "w") as f:
|
1
|
+
"""
|
2
|
+
Configuration management for the Binalyze AIR SDK.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import json
|
7
|
+
from typing import Optional, Dict, Any, List
|
8
|
+
from pydantic import BaseModel, Field, field_validator
|
9
|
+
|
10
|
+
|
11
|
+
class PaginationConfig(BaseModel):
|
12
|
+
"""Pagination configuration."""
|
13
|
+
default_page_size: int = Field(default=10, description="Default page size")
|
14
|
+
max_page_size: int = Field(default=100, description="Maximum page size")
|
15
|
+
default_sort_by: str = Field(default="createdAt", description="Default sort field")
|
16
|
+
default_sort_type: str = Field(default="ASC", description="Default sort type")
|
17
|
+
|
18
|
+
|
19
|
+
class EndpointConfig(BaseModel):
|
20
|
+
"""API endpoint configuration."""
|
21
|
+
acquisitions: Dict[str, str] = Field(default_factory=dict)
|
22
|
+
assets: Dict[str, str] = Field(default_factory=dict)
|
23
|
+
audit: Dict[str, str] = Field(default_factory=dict)
|
24
|
+
auth: Dict[str, str] = Field(default_factory=dict)
|
25
|
+
auto_asset_tags: Dict[str, str] = Field(default_factory=dict)
|
26
|
+
baseline: Dict[str, str] = Field(default_factory=dict)
|
27
|
+
cases: Dict[str, str] = Field(default_factory=dict)
|
28
|
+
policies: Dict[str, str] = Field(default_factory=dict)
|
29
|
+
tasks: Dict[str, str] = Field(default_factory=dict)
|
30
|
+
task_assignments: Dict[str, str] = Field(default_factory=dict)
|
31
|
+
triage_rules: Dict[str, str] = Field(default_factory=dict)
|
32
|
+
organizations: Dict[str, str] = Field(default_factory=dict)
|
33
|
+
users: Dict[str, str] = Field(default_factory=dict)
|
34
|
+
repositories: Dict[str, str] = Field(default_factory=dict)
|
35
|
+
|
36
|
+
|
37
|
+
class DefaultFiltersConfig(BaseModel):
|
38
|
+
"""Default filter configuration."""
|
39
|
+
organization_ids: List[int] = Field(default=[0])
|
40
|
+
all_organizations: bool = Field(default=True)
|
41
|
+
managed_status: List[str] = Field(default=["managed"])
|
42
|
+
online_status: List[str] = Field(default=["online"])
|
43
|
+
sort_type: str = Field(default="ASC")
|
44
|
+
|
45
|
+
|
46
|
+
class TaskDefaultsConfig(BaseModel):
|
47
|
+
"""Task default configuration."""
|
48
|
+
cpu_limit: int = Field(default=80)
|
49
|
+
enable_compression: bool = Field(default=True)
|
50
|
+
enable_encryption: bool = Field(default=False)
|
51
|
+
bandwidth_limit: int = Field(default=100000)
|
52
|
+
chunk_size: int = Field(default=1048576)
|
53
|
+
chunk_count: int = Field(default=0)
|
54
|
+
start_offset: int = Field(default=0)
|
55
|
+
|
56
|
+
|
57
|
+
class LoggingConfig(BaseModel):
|
58
|
+
"""Logging configuration."""
|
59
|
+
level: str = Field(default="INFO")
|
60
|
+
format: str = Field(default="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
61
|
+
file: str = Field(default="air_sdk.log")
|
62
|
+
max_file_size: str = Field(default="10MB")
|
63
|
+
backup_count: int = Field(default=5)
|
64
|
+
|
65
|
+
|
66
|
+
class CacheConfig(BaseModel):
|
67
|
+
"""Cache configuration."""
|
68
|
+
enabled: bool = Field(default=True)
|
69
|
+
ttl: int = Field(default=300)
|
70
|
+
max_size: int = Field(default=1000)
|
71
|
+
|
72
|
+
|
73
|
+
class RateLimitingConfig(BaseModel):
|
74
|
+
"""Rate limiting configuration."""
|
75
|
+
enabled: bool = Field(default=True)
|
76
|
+
requests_per_minute: int = Field(default=100)
|
77
|
+
burst_size: int = Field(default=10)
|
78
|
+
|
79
|
+
|
80
|
+
class AIRConfig(BaseModel):
|
81
|
+
"""Configuration for the AIR SDK."""
|
82
|
+
|
83
|
+
host: str = Field(..., description="AIR instance host URL")
|
84
|
+
api_token: str = Field(..., description="API token for authentication")
|
85
|
+
api_prefix: str = Field(default="api/public", description="API prefix path")
|
86
|
+
organization_id: int = Field(default=0, description="Default organization ID")
|
87
|
+
timeout: int = Field(default=30, description="Request timeout in seconds")
|
88
|
+
verify_ssl: bool = Field(default=True, description="Whether to verify SSL certificates")
|
89
|
+
retry_attempts: int = Field(default=3, description="Number of retry attempts for failed requests")
|
90
|
+
retry_delay: float = Field(default=1.0, description="Delay between retry attempts")
|
91
|
+
|
92
|
+
# Enhanced configuration sections
|
93
|
+
pagination: PaginationConfig = Field(default_factory=PaginationConfig)
|
94
|
+
endpoints: EndpointConfig = Field(default_factory=EndpointConfig)
|
95
|
+
default_filters: DefaultFiltersConfig = Field(default_factory=DefaultFiltersConfig)
|
96
|
+
task_defaults: TaskDefaultsConfig = Field(default_factory=TaskDefaultsConfig)
|
97
|
+
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
98
|
+
cache: CacheConfig = Field(default_factory=CacheConfig)
|
99
|
+
rate_limiting: RateLimitingConfig = Field(default_factory=RateLimitingConfig)
|
100
|
+
|
101
|
+
@field_validator("host")
|
102
|
+
@classmethod
|
103
|
+
def validate_host(cls, v):
|
104
|
+
"""Ensure host URL is properly formatted."""
|
105
|
+
if not v.startswith(("http://", "https://")):
|
106
|
+
raise ValueError("Host must start with http:// or https://")
|
107
|
+
return v.rstrip("/")
|
108
|
+
|
109
|
+
@field_validator("api_token")
|
110
|
+
@classmethod
|
111
|
+
def validate_api_token(cls, v):
|
112
|
+
"""Ensure API token is not empty."""
|
113
|
+
if not v or not v.strip():
|
114
|
+
raise ValueError("API token cannot be empty")
|
115
|
+
return v.strip()
|
116
|
+
|
117
|
+
@property
|
118
|
+
def base_url(self) -> str:
|
119
|
+
"""Get the full base URL for API requests."""
|
120
|
+
return f"{self.host}/{self.api_prefix}"
|
121
|
+
|
122
|
+
def get_endpoint(self, category: str, endpoint: str) -> str:
|
123
|
+
"""Get a specific endpoint URL."""
|
124
|
+
category_endpoints = getattr(self.endpoints, category, {})
|
125
|
+
if isinstance(category_endpoints, dict):
|
126
|
+
return category_endpoints.get(endpoint, "")
|
127
|
+
return ""
|
128
|
+
|
129
|
+
def get_full_endpoint_url(self, category: str, endpoint: str, **kwargs) -> str:
|
130
|
+
"""Get the full URL for an endpoint with substitutions."""
|
131
|
+
endpoint_path = self.get_endpoint(category, endpoint)
|
132
|
+
if not endpoint_path:
|
133
|
+
raise ValueError(f"Endpoint not found: {category}.{endpoint}")
|
134
|
+
|
135
|
+
# Substitute path parameters
|
136
|
+
full_path = endpoint_path.format(**kwargs)
|
137
|
+
return f"{self.base_url}{full_path}"
|
138
|
+
|
139
|
+
@classmethod
|
140
|
+
def from_environment(cls) -> "AIRConfig":
|
141
|
+
"""Create configuration from environment variables."""
|
142
|
+
config_data = {
|
143
|
+
"host": os.getenv("AIR_HOST", ""),
|
144
|
+
"api_token": os.getenv("AIR_API_TOKEN", ""),
|
145
|
+
"api_prefix": os.getenv("AIR_API_PREFIX", "api/public"),
|
146
|
+
"organization_id": int(os.getenv("AIR_ORGANIZATION_ID", "0")),
|
147
|
+
"timeout": int(os.getenv("AIR_TIMEOUT", "30")),
|
148
|
+
"verify_ssl": os.getenv("AIR_VERIFY_SSL", "true").lower() == "true",
|
149
|
+
"retry_attempts": int(os.getenv("AIR_RETRY_ATTEMPTS", "3")),
|
150
|
+
"retry_delay": float(os.getenv("AIR_RETRY_DELAY", "1.0")),
|
151
|
+
}
|
152
|
+
|
153
|
+
return cls(**config_data)
|
154
|
+
|
155
|
+
@classmethod
|
156
|
+
def from_file(cls, config_path: str = "config.json") -> "AIRConfig":
|
157
|
+
"""Create configuration from JSON file."""
|
158
|
+
try:
|
159
|
+
with open(config_path, "r") as f:
|
160
|
+
config_data = json.load(f)
|
161
|
+
return cls(**config_data)
|
162
|
+
except FileNotFoundError:
|
163
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
164
|
+
except json.JSONDecodeError as e:
|
165
|
+
raise ValueError(f"Invalid JSON in configuration file: {e}")
|
166
|
+
|
167
|
+
@classmethod
|
168
|
+
def create(
|
169
|
+
cls,
|
170
|
+
host: Optional[str] = None,
|
171
|
+
api_token: Optional[str] = None,
|
172
|
+
organization_id: Optional[int] = None,
|
173
|
+
config_file: Optional[str] = None,
|
174
|
+
**kwargs
|
175
|
+
) -> "AIRConfig":
|
176
|
+
"""Create configuration with precedence: params > env vars > config file."""
|
177
|
+
config_data = {}
|
178
|
+
|
179
|
+
# 1. Try config file first (lowest precedence)
|
180
|
+
if config_file and os.path.exists(config_file):
|
181
|
+
try:
|
182
|
+
with open(config_file, "r") as f:
|
183
|
+
config_data = json.load(f)
|
184
|
+
except (json.JSONDecodeError, IOError):
|
185
|
+
pass # Ignore file errors, will try other methods
|
186
|
+
elif os.path.exists("config.json"):
|
187
|
+
# Try default config.json
|
188
|
+
try:
|
189
|
+
with open("config.json", "r") as f:
|
190
|
+
config_data = json.load(f)
|
191
|
+
except (json.JSONDecodeError, IOError):
|
192
|
+
pass
|
193
|
+
|
194
|
+
# 2. Override with environment variables
|
195
|
+
env_config = {}
|
196
|
+
if os.getenv("AIR_HOST"):
|
197
|
+
env_config["host"] = os.getenv("AIR_HOST")
|
198
|
+
if os.getenv("AIR_API_TOKEN"):
|
199
|
+
env_config["api_token"] = os.getenv("AIR_API_TOKEN")
|
200
|
+
if os.getenv("AIR_API_PREFIX"):
|
201
|
+
env_config["api_prefix"] = os.getenv("AIR_API_PREFIX")
|
202
|
+
|
203
|
+
org_id_env = os.getenv("AIR_ORGANIZATION_ID")
|
204
|
+
if org_id_env:
|
205
|
+
env_config["organization_id"] = int(org_id_env)
|
206
|
+
|
207
|
+
timeout_env = os.getenv("AIR_TIMEOUT")
|
208
|
+
if timeout_env:
|
209
|
+
env_config["timeout"] = int(timeout_env)
|
210
|
+
|
211
|
+
verify_ssl_env = os.getenv("AIR_VERIFY_SSL")
|
212
|
+
if verify_ssl_env:
|
213
|
+
env_config["verify_ssl"] = verify_ssl_env.lower() == "true"
|
214
|
+
|
215
|
+
retry_attempts_env = os.getenv("AIR_RETRY_ATTEMPTS")
|
216
|
+
if retry_attempts_env:
|
217
|
+
env_config["retry_attempts"] = int(retry_attempts_env)
|
218
|
+
|
219
|
+
retry_delay_env = os.getenv("AIR_RETRY_DELAY")
|
220
|
+
if retry_delay_env:
|
221
|
+
env_config["retry_delay"] = float(retry_delay_env)
|
222
|
+
|
223
|
+
config_data.update(env_config)
|
224
|
+
|
225
|
+
# 3. Override with explicit parameters (highest precedence)
|
226
|
+
if host is not None:
|
227
|
+
config_data["host"] = host
|
228
|
+
if api_token is not None:
|
229
|
+
config_data["api_token"] = api_token
|
230
|
+
if organization_id is not None:
|
231
|
+
config_data["organization_id"] = organization_id
|
232
|
+
|
233
|
+
# Add any additional kwargs
|
234
|
+
config_data.update(kwargs)
|
235
|
+
|
236
|
+
return cls(**config_data)
|
237
|
+
|
238
|
+
def to_dict(self) -> Dict[str, Any]:
|
239
|
+
"""Convert configuration to dictionary."""
|
240
|
+
return self.model_dump()
|
241
|
+
|
242
|
+
def save_to_file(self, config_path: str = "config.json") -> None:
|
243
|
+
"""Save configuration to JSON file."""
|
244
|
+
with open(config_path, "w") as f:
|
245
245
|
json.dump(self.to_dict(), f, indent=2)
|
binalyze_air/exceptions.py
CHANGED
@@ -1,50 +1,50 @@
|
|
1
|
-
"""
|
2
|
-
Custom exceptions for the Binalyze AIR SDK.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from typing import Optional, Dict, Any
|
6
|
-
|
7
|
-
|
8
|
-
class AIRAPIError(Exception):
|
9
|
-
"""Base exception for all AIR API errors."""
|
10
|
-
|
11
|
-
def __init__(self, message: str, status_code: Optional[int] = None, response_data: Optional[Dict[str, Any]] = None):
|
12
|
-
self.message = message
|
13
|
-
self.status_code = status_code
|
14
|
-
self.response_data = response_data or {}
|
15
|
-
super().__init__(self.message)
|
16
|
-
|
17
|
-
|
18
|
-
class AuthenticationError(AIRAPIError):
|
19
|
-
"""Raised when authentication fails."""
|
20
|
-
pass
|
21
|
-
|
22
|
-
|
23
|
-
class AuthorizationError(AIRAPIError):
|
24
|
-
"""Raised when authorization fails."""
|
25
|
-
pass
|
26
|
-
|
27
|
-
|
28
|
-
class NotFoundError(AIRAPIError):
|
29
|
-
"""Raised when a resource is not found."""
|
30
|
-
pass
|
31
|
-
|
32
|
-
|
33
|
-
class ValidationError(AIRAPIError):
|
34
|
-
"""Raised when request validation fails."""
|
35
|
-
pass
|
36
|
-
|
37
|
-
|
38
|
-
class RateLimitError(AIRAPIError):
|
39
|
-
"""Raised when rate limit is exceeded."""
|
40
|
-
pass
|
41
|
-
|
42
|
-
|
43
|
-
class ServerError(AIRAPIError):
|
44
|
-
"""Raised when server returns 5xx status codes."""
|
45
|
-
pass
|
46
|
-
|
47
|
-
|
48
|
-
class NetworkError(AIRAPIError):
|
49
|
-
"""Raised when network-related errors occur."""
|
1
|
+
"""
|
2
|
+
Custom exceptions for the Binalyze AIR SDK.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional, Dict, Any
|
6
|
+
|
7
|
+
|
8
|
+
class AIRAPIError(Exception):
|
9
|
+
"""Base exception for all AIR API errors."""
|
10
|
+
|
11
|
+
def __init__(self, message: str, status_code: Optional[int] = None, response_data: Optional[Dict[str, Any]] = None):
|
12
|
+
self.message = message
|
13
|
+
self.status_code = status_code
|
14
|
+
self.response_data = response_data or {}
|
15
|
+
super().__init__(self.message)
|
16
|
+
|
17
|
+
|
18
|
+
class AuthenticationError(AIRAPIError):
|
19
|
+
"""Raised when authentication fails."""
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class AuthorizationError(AIRAPIError):
|
24
|
+
"""Raised when authorization fails."""
|
25
|
+
pass
|
26
|
+
|
27
|
+
|
28
|
+
class NotFoundError(AIRAPIError):
|
29
|
+
"""Raised when a resource is not found."""
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class ValidationError(AIRAPIError):
|
34
|
+
"""Raised when request validation fails."""
|
35
|
+
pass
|
36
|
+
|
37
|
+
|
38
|
+
class RateLimitError(AIRAPIError):
|
39
|
+
"""Raised when rate limit is exceeded."""
|
40
|
+
pass
|
41
|
+
|
42
|
+
|
43
|
+
class ServerError(AIRAPIError):
|
44
|
+
"""Raised when server returns 5xx status codes."""
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
class NetworkError(AIRAPIError):
|
49
|
+
"""Raised when network-related errors occur."""
|
50
50
|
pass
|