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.
Files changed (142) hide show
  1. binalyze_air/__init__.py +77 -77
  2. binalyze_air/apis/__init__.py +67 -27
  3. binalyze_air/apis/acquisitions.py +107 -0
  4. binalyze_air/apis/api_tokens.py +49 -0
  5. binalyze_air/apis/assets.py +161 -0
  6. binalyze_air/apis/audit_logs.py +26 -0
  7. binalyze_air/apis/{authentication.py → auth.py} +29 -27
  8. binalyze_air/apis/auto_asset_tags.py +79 -75
  9. binalyze_air/apis/backup.py +177 -0
  10. binalyze_air/apis/baseline.py +46 -0
  11. binalyze_air/apis/cases.py +225 -0
  12. binalyze_air/apis/cloud_forensics.py +116 -0
  13. binalyze_air/apis/event_subscription.py +96 -96
  14. binalyze_air/apis/evidence.py +249 -53
  15. binalyze_air/apis/interact.py +153 -36
  16. binalyze_air/apis/investigation_hub.py +234 -0
  17. binalyze_air/apis/license.py +104 -0
  18. binalyze_air/apis/logger.py +83 -0
  19. binalyze_air/apis/multipart_upload.py +201 -0
  20. binalyze_air/apis/notifications.py +115 -0
  21. binalyze_air/apis/organizations.py +267 -0
  22. binalyze_air/apis/params.py +44 -39
  23. binalyze_air/apis/policies.py +186 -0
  24. binalyze_air/apis/preset_filters.py +79 -0
  25. binalyze_air/apis/recent_activities.py +71 -0
  26. binalyze_air/apis/relay_server.py +104 -0
  27. binalyze_air/apis/settings.py +395 -27
  28. binalyze_air/apis/tasks.py +80 -0
  29. binalyze_air/apis/triage.py +197 -0
  30. binalyze_air/apis/user_management.py +183 -74
  31. binalyze_air/apis/webhook_executions.py +50 -0
  32. binalyze_air/apis/webhooks.py +322 -230
  33. binalyze_air/base.py +207 -133
  34. binalyze_air/client.py +217 -1337
  35. binalyze_air/commands/__init__.py +175 -145
  36. binalyze_air/commands/acquisitions.py +661 -387
  37. binalyze_air/commands/api_tokens.py +55 -0
  38. binalyze_air/commands/assets.py +324 -362
  39. binalyze_air/commands/{authentication.py → auth.py} +36 -36
  40. binalyze_air/commands/auto_asset_tags.py +230 -230
  41. binalyze_air/commands/backup.py +47 -0
  42. binalyze_air/commands/baseline.py +32 -396
  43. binalyze_air/commands/cases.py +609 -602
  44. binalyze_air/commands/cloud_forensics.py +88 -0
  45. binalyze_air/commands/event_subscription.py +101 -101
  46. binalyze_air/commands/evidences.py +918 -988
  47. binalyze_air/commands/interact.py +172 -58
  48. binalyze_air/commands/investigation_hub.py +315 -0
  49. binalyze_air/commands/license.py +183 -0
  50. binalyze_air/commands/logger.py +126 -0
  51. binalyze_air/commands/multipart_upload.py +363 -0
  52. binalyze_air/commands/notifications.py +45 -0
  53. binalyze_air/commands/organizations.py +200 -221
  54. binalyze_air/commands/policies.py +175 -203
  55. binalyze_air/commands/preset_filters.py +55 -0
  56. binalyze_air/commands/recent_activities.py +32 -0
  57. binalyze_air/commands/relay_server.py +144 -0
  58. binalyze_air/commands/settings.py +431 -29
  59. binalyze_air/commands/tasks.py +95 -56
  60. binalyze_air/commands/triage.py +224 -360
  61. binalyze_air/commands/user_management.py +351 -126
  62. binalyze_air/commands/webhook_executions.py +77 -0
  63. binalyze_air/config.py +244 -244
  64. binalyze_air/exceptions.py +49 -49
  65. binalyze_air/http_client.py +426 -305
  66. binalyze_air/models/__init__.py +287 -285
  67. binalyze_air/models/acquisitions.py +365 -250
  68. binalyze_air/models/api_tokens.py +73 -0
  69. binalyze_air/models/assets.py +438 -438
  70. binalyze_air/models/audit.py +247 -272
  71. binalyze_air/models/audit_logs.py +14 -0
  72. binalyze_air/models/{authentication.py → auth.py} +69 -69
  73. binalyze_air/models/auto_asset_tags.py +227 -116
  74. binalyze_air/models/backup.py +138 -0
  75. binalyze_air/models/baseline.py +231 -231
  76. binalyze_air/models/cases.py +275 -275
  77. binalyze_air/models/cloud_forensics.py +145 -0
  78. binalyze_air/models/event_subscription.py +170 -171
  79. binalyze_air/models/evidence.py +65 -65
  80. binalyze_air/models/evidences.py +367 -348
  81. binalyze_air/models/interact.py +266 -135
  82. binalyze_air/models/investigation_hub.py +265 -0
  83. binalyze_air/models/license.py +150 -0
  84. binalyze_air/models/logger.py +83 -0
  85. binalyze_air/models/multipart_upload.py +352 -0
  86. binalyze_air/models/notifications.py +138 -0
  87. binalyze_air/models/organizations.py +293 -293
  88. binalyze_air/models/params.py +153 -127
  89. binalyze_air/models/policies.py +260 -249
  90. binalyze_air/models/preset_filters.py +79 -0
  91. binalyze_air/models/recent_activities.py +70 -0
  92. binalyze_air/models/relay_server.py +121 -0
  93. binalyze_air/models/settings.py +538 -84
  94. binalyze_air/models/tasks.py +215 -149
  95. binalyze_air/models/triage.py +141 -142
  96. binalyze_air/models/user_management.py +200 -97
  97. binalyze_air/models/webhook_executions.py +33 -0
  98. binalyze_air/queries/__init__.py +121 -133
  99. binalyze_air/queries/acquisitions.py +155 -155
  100. binalyze_air/queries/api_tokens.py +46 -0
  101. binalyze_air/queries/assets.py +186 -105
  102. binalyze_air/queries/audit.py +400 -416
  103. binalyze_air/queries/{authentication.py → auth.py} +55 -55
  104. binalyze_air/queries/auto_asset_tags.py +59 -59
  105. binalyze_air/queries/backup.py +66 -0
  106. binalyze_air/queries/baseline.py +21 -185
  107. binalyze_air/queries/cases.py +292 -292
  108. binalyze_air/queries/cloud_forensics.py +137 -0
  109. binalyze_air/queries/event_subscription.py +54 -54
  110. binalyze_air/queries/evidence.py +139 -139
  111. binalyze_air/queries/evidences.py +279 -279
  112. binalyze_air/queries/interact.py +140 -28
  113. binalyze_air/queries/investigation_hub.py +329 -0
  114. binalyze_air/queries/license.py +85 -0
  115. binalyze_air/queries/logger.py +58 -0
  116. binalyze_air/queries/multipart_upload.py +180 -0
  117. binalyze_air/queries/notifications.py +71 -0
  118. binalyze_air/queries/organizations.py +222 -222
  119. binalyze_air/queries/params.py +154 -115
  120. binalyze_air/queries/policies.py +149 -149
  121. binalyze_air/queries/preset_filters.py +60 -0
  122. binalyze_air/queries/recent_activities.py +44 -0
  123. binalyze_air/queries/relay_server.py +42 -0
  124. binalyze_air/queries/settings.py +533 -20
  125. binalyze_air/queries/tasks.py +125 -81
  126. binalyze_air/queries/triage.py +230 -230
  127. binalyze_air/queries/user_management.py +193 -83
  128. binalyze_air/queries/webhook_executions.py +39 -0
  129. binalyze_air_sdk-1.0.3.dist-info/METADATA +752 -0
  130. binalyze_air_sdk-1.0.3.dist-info/RECORD +132 -0
  131. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/WHEEL +1 -1
  132. binalyze_air/apis/endpoints.py +0 -22
  133. binalyze_air/apis/evidences.py +0 -216
  134. binalyze_air/apis/users.py +0 -68
  135. binalyze_air/commands/users.py +0 -101
  136. binalyze_air/models/endpoints.py +0 -76
  137. binalyze_air/models/users.py +0 -82
  138. binalyze_air/queries/endpoints.py +0 -25
  139. binalyze_air/queries/users.py +0 -69
  140. binalyze_air_sdk-1.0.1.dist-info/METADATA +0 -635
  141. binalyze_air_sdk-1.0.1.dist-info/RECORD +0 -82
  142. {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)
@@ -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