blackant-sdk 1.0.2__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.
- blackant/__init__.py +31 -0
- blackant/auth/__init__.py +10 -0
- blackant/auth/blackant_auth.py +518 -0
- blackant/auth/keycloak_manager.py +363 -0
- blackant/auth/request_id.py +52 -0
- blackant/auth/role_assignment.py +443 -0
- blackant/auth/tokens.py +57 -0
- blackant/client.py +400 -0
- blackant/config/__init__.py +0 -0
- blackant/config/docker_config.py +457 -0
- blackant/config/keycloak_admin_config.py +107 -0
- blackant/docker/__init__.py +12 -0
- blackant/docker/builder.py +616 -0
- blackant/docker/client.py +983 -0
- blackant/docker/dao.py +462 -0
- blackant/docker/registry.py +172 -0
- blackant/exceptions.py +111 -0
- blackant/http/__init__.py +8 -0
- blackant/http/client.py +125 -0
- blackant/patterns/__init__.py +1 -0
- blackant/patterns/singleton.py +20 -0
- blackant/services/__init__.py +10 -0
- blackant/services/dao.py +414 -0
- blackant/services/registry.py +635 -0
- blackant/utils/__init__.py +8 -0
- blackant/utils/initialization.py +32 -0
- blackant/utils/logging.py +337 -0
- blackant/utils/request_id.py +13 -0
- blackant/utils/store.py +50 -0
- blackant_sdk-1.0.2.dist-info/METADATA +117 -0
- blackant_sdk-1.0.2.dist-info/RECORD +70 -0
- blackant_sdk-1.0.2.dist-info/WHEEL +5 -0
- blackant_sdk-1.0.2.dist-info/top_level.txt +5 -0
- calculation/__init__.py +0 -0
- calculation/base.py +26 -0
- calculation/errors.py +2 -0
- calculation/impl/__init__.py +0 -0
- calculation/impl/my_calculation.py +144 -0
- calculation/impl/simple_calc.py +53 -0
- calculation/impl/test.py +1 -0
- calculation/impl/test_calc.py +36 -0
- calculation/loader.py +227 -0
- notifinations/__init__.py +8 -0
- notifinations/mail_sender.py +212 -0
- storage/__init__.py +0 -0
- storage/errors.py +10 -0
- storage/factory.py +26 -0
- storage/interface.py +19 -0
- storage/minio.py +106 -0
- task/__init__.py +0 -0
- task/dao.py +38 -0
- task/errors.py +10 -0
- task/log_adapter.py +11 -0
- task/parsers/__init__.py +0 -0
- task/parsers/base.py +13 -0
- task/parsers/callback.py +40 -0
- task/parsers/cmd_args.py +52 -0
- task/parsers/freetext.py +19 -0
- task/parsers/objects.py +50 -0
- task/parsers/request.py +56 -0
- task/resource.py +84 -0
- task/states/__init__.py +0 -0
- task/states/base.py +14 -0
- task/states/error.py +47 -0
- task/states/idle.py +12 -0
- task/states/ready.py +51 -0
- task/states/running.py +21 -0
- task/states/set_up.py +40 -0
- task/states/tear_down.py +29 -0
- task/task.py +358 -0
blackant/client.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""BlackAnt SDK unified client interface.
|
|
2
|
+
|
|
3
|
+
Main entry point for BlackAnt SDK providing all 5 core methods
|
|
4
|
+
through a single unified interface for simplified usage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, Optional, List
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from .auth.blackant_auth import BlackAntAuth
|
|
11
|
+
from .docker.client import BlackAntDockerClient
|
|
12
|
+
from .services.registry import ServiceRegistry
|
|
13
|
+
from .http.client import HTTPClient, HTTPConnectionError
|
|
14
|
+
from .exceptions import BlackAntDockerError, BlackAntConnectionError
|
|
15
|
+
from .utils.logging import get_logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BlackAntClient:
|
|
19
|
+
"""Unified BlackAnt SDK client providing all core functionality.
|
|
20
|
+
|
|
21
|
+
This is the main entry point for the BlackAnt SDK, combining all
|
|
22
|
+
core functionality into a single, easy-to-use interface. It provides
|
|
23
|
+
the 5 core methods that make up the complete BlackAnt SDK experience.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
user: BlackAnt username or email
|
|
27
|
+
password: BlackAnt password
|
|
28
|
+
login_url: Optional login URL override
|
|
29
|
+
base_url: Optional base URL override
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
>>> # Initialize client
|
|
33
|
+
>>> client = BlackAntClient(user="developer@company.com", password="xxx")
|
|
34
|
+
|
|
35
|
+
>>> # Test connection
|
|
36
|
+
>>> if client.test_connection():
|
|
37
|
+
... print("Connected to BlackAnt!")
|
|
38
|
+
|
|
39
|
+
>>> # Build and deploy service
|
|
40
|
+
>>> result = client.build_service("my-calculation")
|
|
41
|
+
>>> print(f"Service deployed: {result['service_id']}")
|
|
42
|
+
|
|
43
|
+
>>> # List all services
|
|
44
|
+
>>> services = client.list_services()
|
|
45
|
+
>>> print(f"Found {len(services)} services")
|
|
46
|
+
|
|
47
|
+
>>> # Register additional service
|
|
48
|
+
>>> service_id = client.register_service({
|
|
49
|
+
... "name": "external-service",
|
|
50
|
+
... "type": "api"
|
|
51
|
+
... })
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self,
|
|
55
|
+
user: str = None,
|
|
56
|
+
password: str = None,
|
|
57
|
+
token: str = None,
|
|
58
|
+
client_id: str = None,
|
|
59
|
+
client_secret: str = None,
|
|
60
|
+
login_url: str = None,
|
|
61
|
+
base_url: str = None):
|
|
62
|
+
"""Initialize unified BlackAnt client.
|
|
63
|
+
|
|
64
|
+
Supports three authentication modes:
|
|
65
|
+
1. Username/Password: Provide user + password
|
|
66
|
+
2. Client Credentials: Provide client_id + client_secret (for automation/CI/CD)
|
|
67
|
+
3. Existing Token: Provide user + token
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
user: BlackAnt username or email (required for password/token auth)
|
|
71
|
+
password: BlackAnt password (password flow)
|
|
72
|
+
token: Existing JWT token (token flow)
|
|
73
|
+
client_id: Service account client ID (client credentials flow)
|
|
74
|
+
client_secret: Service account client secret (client credentials flow)
|
|
75
|
+
login_url: Optional Keycloak login URL override
|
|
76
|
+
base_url: Optional BlackAnt API base URL override
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
>>> # Password authentication
|
|
80
|
+
>>> client = BlackAntClient(user="dev@company.com", password="xxx")
|
|
81
|
+
|
|
82
|
+
>>> # Client credentials authentication (for automation)
|
|
83
|
+
>>> client = BlackAntClient(
|
|
84
|
+
... client_id="my-service-account",
|
|
85
|
+
... client_secret="xyz123..."
|
|
86
|
+
... )
|
|
87
|
+
|
|
88
|
+
>>> # Existing token
|
|
89
|
+
>>> client = BlackAntClient(user="dev@company.com", token="eyJhbG...")
|
|
90
|
+
"""
|
|
91
|
+
self.logger = get_logger("blackant.client")
|
|
92
|
+
|
|
93
|
+
# Initialize authentication based on provided credentials
|
|
94
|
+
if client_id and client_secret:
|
|
95
|
+
# Client Credentials Flow (service account)
|
|
96
|
+
self.auth = BlackAntAuth(
|
|
97
|
+
client_id=client_id,
|
|
98
|
+
client_secret=client_secret,
|
|
99
|
+
login_url=login_url
|
|
100
|
+
)
|
|
101
|
+
elif user and password:
|
|
102
|
+
# Password Flow (username/password)
|
|
103
|
+
self.auth = BlackAntAuth(user=user, password=password, login_url=login_url)
|
|
104
|
+
elif user and token:
|
|
105
|
+
# Existing token
|
|
106
|
+
self.auth = BlackAntAuth(user=user, password="dummy", login_url=login_url)
|
|
107
|
+
self.auth.token_store.user_token = token
|
|
108
|
+
self.auth._authenticated = True
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"Provide either: (user+password), (client_id+client_secret), or (user+token)"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Initialize specialized clients
|
|
115
|
+
self.docker_client = BlackAntDockerClient(auth=self.auth, base_url=base_url)
|
|
116
|
+
self.service_registry = ServiceRegistry(auth=self.auth, base_url=base_url)
|
|
117
|
+
|
|
118
|
+
# Initialize HTTP client for connection testing
|
|
119
|
+
self.base_url = base_url or os.getenv("BLACKANT_BASE_URL", "https://dev.blackant.app")
|
|
120
|
+
self.http_client = HTTPClient(base_url=self.base_url, auth_token_store=self.auth.token_store)
|
|
121
|
+
|
|
122
|
+
self.logger.info("BlackAnt unified client initialized")
|
|
123
|
+
|
|
124
|
+
# ==========================================
|
|
125
|
+
# CORE METHOD 1: AUTHENTICATION
|
|
126
|
+
# ==========================================
|
|
127
|
+
def authenticate(self) -> bool:
|
|
128
|
+
"""Authenticate with BlackAnt platform.
|
|
129
|
+
|
|
130
|
+
Performs authentication with BlackAnt Keycloak and stores
|
|
131
|
+
the JWT token for subsequent API calls.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
bool: True if authentication successful, False otherwise
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
BlackAntAuthenticationError: If authentication fails
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
>>> client = BlackAntClient(user="dev@company.com", password="xxx")
|
|
141
|
+
>>> if client.authenticate():
|
|
142
|
+
... print("Successfully authenticated!")
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
token = self.auth.authenticate()
|
|
146
|
+
self.logger.info("Authentication successful")
|
|
147
|
+
return bool(token)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
self.logger.error(f"Authentication failed: {e}")
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
# ==========================================
|
|
153
|
+
# CORE METHOD 2: CONNECTION TEST
|
|
154
|
+
# ==========================================
|
|
155
|
+
def test_connection(self) -> bool:
|
|
156
|
+
"""Test connection to BlackAnt platform.
|
|
157
|
+
|
|
158
|
+
Verifies that the client can connect to BlackAnt services
|
|
159
|
+
and that authentication is working properly.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
bool: True if connection successful, False otherwise
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
>>> client = BlackAntClient(user="dev@company.com", password="xxx")
|
|
166
|
+
>>> if client.test_connection():
|
|
167
|
+
... print("Connection to BlackAnt successful!")
|
|
168
|
+
... else:
|
|
169
|
+
... print("Connection failed")
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
# Ensure we have a valid token
|
|
173
|
+
if not self.auth.is_authenticated():
|
|
174
|
+
self.authenticate()
|
|
175
|
+
|
|
176
|
+
# Test Docker API connection
|
|
177
|
+
version = self.docker_client.version
|
|
178
|
+
self.logger.info(f"Docker API connection successful, version: {version}")
|
|
179
|
+
|
|
180
|
+
# Test Service Registry connection
|
|
181
|
+
services = self.service_registry.list()
|
|
182
|
+
self.logger.info(f"Service Registry connection successful, found {len(services)} services")
|
|
183
|
+
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
self.logger.error(f"Connection test failed: {e}")
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
# ==========================================
|
|
191
|
+
# CORE METHOD 3: BUILD SERVICE
|
|
192
|
+
# ==========================================
|
|
193
|
+
def build_service(self,
|
|
194
|
+
service_name: str,
|
|
195
|
+
impl_path: str = "src/calculation/impl",
|
|
196
|
+
dockerfile_path: str = "Dockerfile",
|
|
197
|
+
tag: Optional[str] = None,
|
|
198
|
+
push: bool = True,
|
|
199
|
+
register_service: bool = True) -> Dict[str, Any]:
|
|
200
|
+
"""Build and deploy service Docker image.
|
|
201
|
+
|
|
202
|
+
Builds a Docker image from user implementation, pushes to registry,
|
|
203
|
+
and registers the service with ServiceManager. This is the primary
|
|
204
|
+
method for deploying calculations to BlackAnt platform.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
service_name: Service name (alphanumeric + hyphens/underscores)
|
|
208
|
+
impl_path: User implementation folder path
|
|
209
|
+
dockerfile_path: Dockerfile path (repository root)
|
|
210
|
+
tag: Version tag (auto-generated if None: v1.0.0-{timestamp})
|
|
211
|
+
push: Upload to env.blackant.app registry
|
|
212
|
+
register_service: Automatic service registration with ServiceManager
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
dict: Build and deployment information
|
|
216
|
+
{
|
|
217
|
+
"image_name": "env.blackant.app/systemdevelopers/service_name",
|
|
218
|
+
"tag": "v1.0.0-20250105-123456",
|
|
219
|
+
"full_image": "env.blackant.app/systemdevelopers/service_name:v1.0.0-20250105-123456",
|
|
220
|
+
"image_id": "sha256:...",
|
|
221
|
+
"size": 125000000,
|
|
222
|
+
"pushed": True,
|
|
223
|
+
"registry_url": "env.blackant.app",
|
|
224
|
+
"service_id": "uuid-from-servicemanager",
|
|
225
|
+
"service_registered": True,
|
|
226
|
+
"build_success": True
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
BlackAntDockerError: Build, push or registration error
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
>>> client = BlackAntClient(user="developer", password="xxx")
|
|
234
|
+
>>> result = client.build_service("my-calculation")
|
|
235
|
+
>>> if result["build_success"]:
|
|
236
|
+
... print(f"Service deployed with ID: {result['service_id']}")
|
|
237
|
+
"""
|
|
238
|
+
return self.docker_client.build_service(
|
|
239
|
+
service_name=service_name,
|
|
240
|
+
impl_path=impl_path,
|
|
241
|
+
dockerfile_path=dockerfile_path,
|
|
242
|
+
tag=tag,
|
|
243
|
+
push=push,
|
|
244
|
+
register_service=register_service
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# ==========================================
|
|
248
|
+
# CORE METHOD 4: REGISTER SERVICE
|
|
249
|
+
# ==========================================
|
|
250
|
+
def register_service(self, service_data: Dict[str, Any]) -> str:
|
|
251
|
+
"""Register a service with BlackAnt platform.
|
|
252
|
+
|
|
253
|
+
Registers a service with the ServiceManager, making it available
|
|
254
|
+
for discovery and management through the BlackAnt platform.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
service_data: Service registration data containing:
|
|
258
|
+
- name: Service name
|
|
259
|
+
- type: Service type (e.g., "calculation", "api")
|
|
260
|
+
- metadata: Additional service metadata
|
|
261
|
+
- image: Optional Docker image information
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
str: Service ID of the registered service
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
BlackAntDockerError: If service registration fails
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> client = BlackAntClient(user="developer", password="xxx")
|
|
271
|
+
>>> service_id = client.register_service({
|
|
272
|
+
... "name": "external-api",
|
|
273
|
+
... "type": "api",
|
|
274
|
+
... "metadata": {"version": "2.0.0"}
|
|
275
|
+
... })
|
|
276
|
+
>>> print(f"Service registered with ID: {service_id}")
|
|
277
|
+
"""
|
|
278
|
+
return self.service_registry.register(service_data)
|
|
279
|
+
|
|
280
|
+
# ==========================================
|
|
281
|
+
# CORE METHOD 5: LIST SERVICES
|
|
282
|
+
# ==========================================
|
|
283
|
+
def list_services(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
284
|
+
"""List all services, optionally filtered by namespace.
|
|
285
|
+
|
|
286
|
+
Retrieves a list of all services registered with BlackAnt platform,
|
|
287
|
+
optionally filtered by namespace for organization.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
namespace: Optional namespace filter
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of service dictionaries with metadata
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
>>> client = BlackAntClient(user="developer", password="xxx")
|
|
297
|
+
>>> services = client.list_services()
|
|
298
|
+
>>> print(f"Found {len(services)} services")
|
|
299
|
+
>>> for service in services:
|
|
300
|
+
... print(f"- {service['name']}: {service['status']}")
|
|
301
|
+
"""
|
|
302
|
+
return self.service_registry.list(namespace=namespace)
|
|
303
|
+
|
|
304
|
+
# ==========================================
|
|
305
|
+
# ADDITIONAL UTILITY METHODS
|
|
306
|
+
# ==========================================
|
|
307
|
+
def get_service(self, service_id: str = None, service_name: str = None) -> Dict[str, Any]:
|
|
308
|
+
"""Get detailed information about a specific service.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
service_id: Service UUID
|
|
312
|
+
service_name: Service name
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Service details dictionary
|
|
316
|
+
|
|
317
|
+
Example:
|
|
318
|
+
>>> service = client.get_service(service_name="my-calculation")
|
|
319
|
+
>>> print(f"Service status: {service['status']}")
|
|
320
|
+
"""
|
|
321
|
+
return self.service_registry.get(service_id=service_id, service_name=service_name)
|
|
322
|
+
|
|
323
|
+
def update_service(self, service_id: str = None, service_name: str = None,
|
|
324
|
+
update_data: Dict[str, Any] = None) -> bool:
|
|
325
|
+
"""Update service metadata.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
service_id: Service UUID
|
|
329
|
+
service_name: Service name
|
|
330
|
+
update_data: Updated service data
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
bool: True if update successful
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
>>> success = client.update_service(
|
|
337
|
+
... service_name="my-calculation",
|
|
338
|
+
... update_data={"metadata": {"version": "2.0.0"}}
|
|
339
|
+
... )
|
|
340
|
+
"""
|
|
341
|
+
return self.service_registry.update(
|
|
342
|
+
service_id=service_id,
|
|
343
|
+
service_name=service_name,
|
|
344
|
+
update_data=update_data
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def delete_service(self, service_id: str = None, service_name: str = None) -> bool:
|
|
348
|
+
"""Delete a service.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
service_id: Service UUID
|
|
352
|
+
service_name: Service name
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
bool: True if deletion successful
|
|
356
|
+
|
|
357
|
+
Example:
|
|
358
|
+
>>> success = client.delete_service(service_name="old-calculation")
|
|
359
|
+
"""
|
|
360
|
+
return self.service_registry.delete(service_id=service_id, service_name=service_name)
|
|
361
|
+
|
|
362
|
+
def get_service_status(self, service_id: str = None, service_name: str = None) -> Dict[str, Any]:
|
|
363
|
+
"""Get runtime status of a service.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
service_id: Service UUID
|
|
367
|
+
service_name: Service name
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Service status information
|
|
371
|
+
|
|
372
|
+
Example:
|
|
373
|
+
>>> status = client.get_service_status("my-calculation")
|
|
374
|
+
>>> print(f"Service is {status['state']}")
|
|
375
|
+
"""
|
|
376
|
+
return self.service_registry.get_status(service_id=service_id, service_name=service_name)
|
|
377
|
+
|
|
378
|
+
def is_authenticated(self) -> bool:
|
|
379
|
+
"""Check if client is currently authenticated.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
bool: True if authenticated with valid token
|
|
383
|
+
"""
|
|
384
|
+
return self.auth.is_authenticated()
|
|
385
|
+
|
|
386
|
+
def __repr__(self) -> str:
|
|
387
|
+
"""String representation of client."""
|
|
388
|
+
if hasattr(self.auth, '_auth_mode') and self.auth._auth_mode == "client_credentials":
|
|
389
|
+
return f"BlackAntClient(client_id={self.auth.client_id}, authenticated={self.is_authenticated()})"
|
|
390
|
+
else:
|
|
391
|
+
return f"BlackAntClient(user={self.auth.user}, authenticated={self.is_authenticated()})"
|
|
392
|
+
|
|
393
|
+
def __enter__(self):
|
|
394
|
+
"""Context manager entry."""
|
|
395
|
+
return self
|
|
396
|
+
|
|
397
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
398
|
+
"""Context manager exit."""
|
|
399
|
+
# Cleanup if needed
|
|
400
|
+
pass
|
|
File without changes
|