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.
Files changed (70) hide show
  1. blackant/__init__.py +31 -0
  2. blackant/auth/__init__.py +10 -0
  3. blackant/auth/blackant_auth.py +518 -0
  4. blackant/auth/keycloak_manager.py +363 -0
  5. blackant/auth/request_id.py +52 -0
  6. blackant/auth/role_assignment.py +443 -0
  7. blackant/auth/tokens.py +57 -0
  8. blackant/client.py +400 -0
  9. blackant/config/__init__.py +0 -0
  10. blackant/config/docker_config.py +457 -0
  11. blackant/config/keycloak_admin_config.py +107 -0
  12. blackant/docker/__init__.py +12 -0
  13. blackant/docker/builder.py +616 -0
  14. blackant/docker/client.py +983 -0
  15. blackant/docker/dao.py +462 -0
  16. blackant/docker/registry.py +172 -0
  17. blackant/exceptions.py +111 -0
  18. blackant/http/__init__.py +8 -0
  19. blackant/http/client.py +125 -0
  20. blackant/patterns/__init__.py +1 -0
  21. blackant/patterns/singleton.py +20 -0
  22. blackant/services/__init__.py +10 -0
  23. blackant/services/dao.py +414 -0
  24. blackant/services/registry.py +635 -0
  25. blackant/utils/__init__.py +8 -0
  26. blackant/utils/initialization.py +32 -0
  27. blackant/utils/logging.py +337 -0
  28. blackant/utils/request_id.py +13 -0
  29. blackant/utils/store.py +50 -0
  30. blackant_sdk-1.0.2.dist-info/METADATA +117 -0
  31. blackant_sdk-1.0.2.dist-info/RECORD +70 -0
  32. blackant_sdk-1.0.2.dist-info/WHEEL +5 -0
  33. blackant_sdk-1.0.2.dist-info/top_level.txt +5 -0
  34. calculation/__init__.py +0 -0
  35. calculation/base.py +26 -0
  36. calculation/errors.py +2 -0
  37. calculation/impl/__init__.py +0 -0
  38. calculation/impl/my_calculation.py +144 -0
  39. calculation/impl/simple_calc.py +53 -0
  40. calculation/impl/test.py +1 -0
  41. calculation/impl/test_calc.py +36 -0
  42. calculation/loader.py +227 -0
  43. notifinations/__init__.py +8 -0
  44. notifinations/mail_sender.py +212 -0
  45. storage/__init__.py +0 -0
  46. storage/errors.py +10 -0
  47. storage/factory.py +26 -0
  48. storage/interface.py +19 -0
  49. storage/minio.py +106 -0
  50. task/__init__.py +0 -0
  51. task/dao.py +38 -0
  52. task/errors.py +10 -0
  53. task/log_adapter.py +11 -0
  54. task/parsers/__init__.py +0 -0
  55. task/parsers/base.py +13 -0
  56. task/parsers/callback.py +40 -0
  57. task/parsers/cmd_args.py +52 -0
  58. task/parsers/freetext.py +19 -0
  59. task/parsers/objects.py +50 -0
  60. task/parsers/request.py +56 -0
  61. task/resource.py +84 -0
  62. task/states/__init__.py +0 -0
  63. task/states/base.py +14 -0
  64. task/states/error.py +47 -0
  65. task/states/idle.py +12 -0
  66. task/states/ready.py +51 -0
  67. task/states/running.py +21 -0
  68. task/states/set_up.py +40 -0
  69. task/states/tear_down.py +29 -0
  70. 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