opensecureconf-client 2.3.1__py3-none-any.whl → 3.0.0__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.
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/METADATA +386 -207
- opensecureconf_client-3.0.0.dist-info/RECORD +6 -0
- opensecureconf_client.py +198 -101
- opensecureconf_client-2.3.1.dist-info/RECORD +0 -6
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/WHEEL +0 -0
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
opensecureconf_client.py,sha256=pnMDS-UW5pr8xxJS6gaePIdn2vQX6INlLeRHU4PTjTY,34396
|
|
2
|
+
opensecureconf_client-3.0.0.dist-info/licenses/LICENSE,sha256=mvMdzinneV_-L01ddrHOBgbutNS8tjT1m7loT7VTWbI,1073
|
|
3
|
+
opensecureconf_client-3.0.0.dist-info/METADATA,sha256=SPhMNSD9P9BjwNoaH2067MimKgb1K5DBTaiOLyept1Q,42145
|
|
4
|
+
opensecureconf_client-3.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
+
opensecureconf_client-3.0.0.dist-info/top_level.txt,sha256=J7NP3hD92OUdqseJLlbzgPuG_ovqkURRyw7iBJJeDVE,22
|
|
6
|
+
opensecureconf_client-3.0.0.dist-info/RECORD,,
|
opensecureconf_client.py
CHANGED
|
@@ -13,7 +13,8 @@ Enhanced Features:
|
|
|
13
13
|
- Enhanced input validation
|
|
14
14
|
- Health check utilities
|
|
15
15
|
- Support for multiple value types (dict, str, int, bool, list)
|
|
16
|
-
-
|
|
16
|
+
- Multi-environment support (same key in different environments)
|
|
17
|
+
- Environment-based configuration isolation
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
from typing import Any, Dict, List, Optional, Union
|
|
@@ -24,29 +25,37 @@ from requests.adapters import HTTPAdapter
|
|
|
24
25
|
from requests.exceptions import Timeout, RequestException
|
|
25
26
|
from urllib3.util.retry import Retry
|
|
26
27
|
|
|
28
|
+
|
|
27
29
|
# ============================================================================
|
|
28
30
|
# EXCEPTIONS
|
|
29
31
|
# ============================================================================
|
|
30
32
|
|
|
33
|
+
|
|
31
34
|
class OpenSecureConfError(Exception):
|
|
32
35
|
"""Base exception for OpenSecureConf client errors."""
|
|
33
36
|
|
|
37
|
+
|
|
34
38
|
class AuthenticationError(OpenSecureConfError):
|
|
35
39
|
"""Raised when authentication fails (invalid or missing user key)."""
|
|
36
40
|
|
|
41
|
+
|
|
37
42
|
class ConfigurationNotFoundError(OpenSecureConfError):
|
|
38
43
|
"""Raised when a requested configuration key does not exist."""
|
|
39
44
|
|
|
45
|
+
|
|
40
46
|
class ConfigurationExistsError(OpenSecureConfError):
|
|
41
47
|
"""Raised when attempting to create a configuration that already exists."""
|
|
42
48
|
|
|
49
|
+
|
|
43
50
|
class ClusterError(OpenSecureConfError):
|
|
44
51
|
"""Raised when cluster operations fail."""
|
|
45
52
|
|
|
53
|
+
|
|
46
54
|
# ============================================================================
|
|
47
55
|
# CLIENT
|
|
48
56
|
# ============================================================================
|
|
49
57
|
|
|
58
|
+
|
|
50
59
|
class OpenSecureConfClient:
|
|
51
60
|
"""
|
|
52
61
|
Enhanced client for interacting with the OpenSecureConf API.
|
|
@@ -69,12 +78,11 @@ class OpenSecureConfClient:
|
|
|
69
78
|
... enable_retry=True,
|
|
70
79
|
... log_level="INFO"
|
|
71
80
|
... )
|
|
72
|
-
>>> #
|
|
73
|
-
>>>
|
|
74
|
-
...
|
|
75
|
-
>>>
|
|
76
|
-
|
|
77
|
-
... category="auth", environment="staging")
|
|
81
|
+
>>> # Same key in different environments
|
|
82
|
+
>>> prod_config = client.create("database", {"host": "db.prod.com", "port": 5432},
|
|
83
|
+
... "production", "config")
|
|
84
|
+
>>> staging_config = client.create("database", {"host": "db.staging.com", "port": 5432},
|
|
85
|
+
... "staging", "config")
|
|
78
86
|
"""
|
|
79
87
|
|
|
80
88
|
def __init__(
|
|
@@ -349,143 +357,163 @@ class OpenSecureConfClient:
|
|
|
349
357
|
self,
|
|
350
358
|
key: str,
|
|
351
359
|
value: Union[Dict[str, Any], str, int, bool, list],
|
|
352
|
-
|
|
353
|
-
|
|
360
|
+
environment: str,
|
|
361
|
+
category: Optional[str] = None
|
|
354
362
|
) -> Dict[str, Any]:
|
|
355
363
|
"""
|
|
356
364
|
Create a new encrypted configuration entry.
|
|
357
365
|
|
|
358
366
|
Args:
|
|
359
|
-
key:
|
|
367
|
+
key: Configuration key (1-255 characters)
|
|
360
368
|
value: Configuration data (dict, string, int, bool, or list - will be encrypted)
|
|
369
|
+
environment: Environment identifier (REQUIRED, max 100 characters)
|
|
361
370
|
category: Optional category for grouping (max 100 characters)
|
|
362
|
-
environment: Optional environment identifier (max 100 characters)
|
|
363
371
|
|
|
364
372
|
Returns:
|
|
365
373
|
Dictionary containing the created configuration with fields:
|
|
366
374
|
- id: Configuration ID
|
|
367
375
|
- key: Configuration key
|
|
368
376
|
- value: Configuration value (decrypted)
|
|
377
|
+
- environment: Environment identifier
|
|
369
378
|
- category: Configuration category (if set)
|
|
370
|
-
- environment: Environment identifier (if set)
|
|
371
379
|
|
|
372
380
|
Raises:
|
|
373
|
-
ConfigurationExistsError: If configuration key already exists
|
|
374
|
-
ValueError: If key is invalid
|
|
381
|
+
ConfigurationExistsError: If configuration (key, environment) already exists
|
|
382
|
+
ValueError: If key or environment is invalid
|
|
375
383
|
|
|
376
384
|
Example:
|
|
377
|
-
>>> #
|
|
378
|
-
>>>
|
|
379
|
-
...
|
|
385
|
+
>>> # Same key in different environments
|
|
386
|
+
>>> prod_config = client.create("database", {"host": "db.prod.com", "port": 5432},
|
|
387
|
+
... "production", "config")
|
|
388
|
+
>>> staging_config = client.create("database", {"host": "db.staging.com", "port": 5432},
|
|
389
|
+
... "staging", "config")
|
|
380
390
|
>>> # String value
|
|
381
|
-
>>>
|
|
382
|
-
... category="auth", environment="staging")
|
|
391
|
+
>>> client.create("api_token", "secret-123", "production", "auth")
|
|
383
392
|
>>> # Integer value
|
|
384
|
-
>>>
|
|
393
|
+
>>> client.create("max_retries", 3, "production")
|
|
385
394
|
>>> # Boolean value
|
|
386
|
-
>>>
|
|
395
|
+
>>> client.create("debug", False, "development", "settings")
|
|
387
396
|
"""
|
|
388
397
|
# Enhanced validation
|
|
389
398
|
if not key or not isinstance(key, str):
|
|
390
399
|
raise ValueError("Key must be a non-empty string")
|
|
391
400
|
if len(key) > 255:
|
|
392
401
|
raise ValueError("Key must be between 1 and 255 characters")
|
|
402
|
+
if not environment or not isinstance(environment, str):
|
|
403
|
+
raise ValueError("Environment is required and must be a non-empty string")
|
|
404
|
+
if len(environment) > 100:
|
|
405
|
+
raise ValueError("Environment must be max 100 characters")
|
|
393
406
|
if category and len(category) > 100:
|
|
394
407
|
raise ValueError("Category must be max 100 characters")
|
|
395
|
-
if environment and len(environment) > 100:
|
|
396
|
-
raise ValueError("Environment must be max 100 characters")
|
|
397
408
|
|
|
398
|
-
payload = {
|
|
409
|
+
payload = {
|
|
410
|
+
"key": key,
|
|
411
|
+
"value": value,
|
|
412
|
+
"environment": environment,
|
|
413
|
+
"category": category
|
|
414
|
+
}
|
|
399
415
|
return self._make_request("POST", "/configs", json=payload)
|
|
400
416
|
|
|
401
|
-
def read(self, key: str) -> Dict[str, Any]:
|
|
417
|
+
def read(self, key: str, environment: str) -> Dict[str, Any]:
|
|
402
418
|
"""
|
|
403
|
-
Read and decrypt a configuration entry by key.
|
|
419
|
+
Read and decrypt a configuration entry by key and environment.
|
|
404
420
|
|
|
405
421
|
Args:
|
|
406
422
|
key: Configuration key to retrieve
|
|
423
|
+
environment: Environment identifier (REQUIRED)
|
|
407
424
|
|
|
408
425
|
Returns:
|
|
409
426
|
Dictionary containing the configuration with decrypted value
|
|
410
427
|
The value can be dict, str, int, bool, or list depending on what was stored
|
|
411
428
|
|
|
412
429
|
Raises:
|
|
413
|
-
ConfigurationNotFoundError: If configuration key does not exist
|
|
414
|
-
ValueError: If key is invalid
|
|
430
|
+
ConfigurationNotFoundError: If configuration (key, environment) does not exist
|
|
431
|
+
ValueError: If key or environment is invalid
|
|
415
432
|
|
|
416
433
|
Example:
|
|
417
|
-
>>>
|
|
418
|
-
>>>
|
|
419
|
-
>>> print(
|
|
434
|
+
>>> prod_config = client.read("database", "production")
|
|
435
|
+
>>> staging_config = client.read("database", "staging")
|
|
436
|
+
>>> print(prod_config["value"]) # Different from staging
|
|
437
|
+
>>> print(prod_config["environment"]) # "production"
|
|
420
438
|
"""
|
|
421
439
|
if not key or not isinstance(key, str):
|
|
422
440
|
raise ValueError("Key must be a non-empty string")
|
|
441
|
+
if not environment or not isinstance(environment, str):
|
|
442
|
+
raise ValueError("Environment is required and must be a non-empty string")
|
|
423
443
|
|
|
424
|
-
|
|
444
|
+
params = {"environment": environment}
|
|
445
|
+
return self._make_request("GET", f"/configs/{key}", params=params)
|
|
425
446
|
|
|
426
447
|
def update(
|
|
427
448
|
self,
|
|
428
449
|
key: str,
|
|
450
|
+
environment: str,
|
|
429
451
|
value: Union[Dict[str, Any], str, int, bool, list],
|
|
430
|
-
category: Optional[str] = None
|
|
431
|
-
environment: Optional[str] = None
|
|
452
|
+
category: Optional[str] = None
|
|
432
453
|
) -> Dict[str, Any]:
|
|
433
454
|
"""
|
|
434
455
|
Update an existing configuration entry with new encrypted value.
|
|
435
456
|
|
|
436
457
|
Args:
|
|
437
458
|
key: Configuration key to update
|
|
459
|
+
environment: Environment identifier (REQUIRED, cannot be changed)
|
|
438
460
|
value: New configuration data (dict, string, int, bool, or list - will be encrypted)
|
|
439
461
|
category: Optional new category
|
|
440
|
-
environment: Optional new environment
|
|
441
462
|
|
|
442
463
|
Returns:
|
|
443
464
|
Dictionary containing the updated configuration with decrypted value
|
|
444
465
|
|
|
445
466
|
Raises:
|
|
446
|
-
ConfigurationNotFoundError: If configuration key does not exist
|
|
447
|
-
ValueError: If key is invalid
|
|
467
|
+
ConfigurationNotFoundError: If configuration (key, environment) does not exist
|
|
468
|
+
ValueError: If key or environment is invalid
|
|
448
469
|
|
|
449
470
|
Example:
|
|
450
|
-
>>> # Update
|
|
451
|
-
>>> config = client.update("database",
|
|
452
|
-
...
|
|
453
|
-
>>> # Update with string and
|
|
454
|
-
>>> config = client.update("api_token", "new-token-456",
|
|
471
|
+
>>> # Update production config only
|
|
472
|
+
>>> config = client.update("database", "production",
|
|
473
|
+
... {"host": "db-new.prod.com", "port": 5432})
|
|
474
|
+
>>> # Update with string and category
|
|
475
|
+
>>> config = client.update("api_token", "staging", "new-token-456", "auth")
|
|
455
476
|
"""
|
|
456
477
|
if not key or not isinstance(key, str):
|
|
457
478
|
raise ValueError("Key must be a non-empty string")
|
|
479
|
+
if not environment or not isinstance(environment, str):
|
|
480
|
+
raise ValueError("Environment is required and must be a non-empty string")
|
|
458
481
|
if category and len(category) > 100:
|
|
459
482
|
raise ValueError("Category must be max 100 characters")
|
|
460
|
-
if environment and len(environment) > 100:
|
|
461
|
-
raise ValueError("Environment must be max 100 characters")
|
|
462
483
|
|
|
463
|
-
payload = {"value": value, "category": category
|
|
464
|
-
|
|
484
|
+
payload = {"value": value, "category": category}
|
|
485
|
+
params = {"environment": environment}
|
|
486
|
+
return self._make_request("PUT", f"/configs/{key}", json=payload, params=params)
|
|
465
487
|
|
|
466
|
-
def delete(self, key: str) -> Dict[str, str]:
|
|
488
|
+
def delete(self, key: str, environment: str) -> Dict[str, str]:
|
|
467
489
|
"""
|
|
468
|
-
Delete a configuration entry permanently.
|
|
490
|
+
Delete a configuration entry permanently from specific environment.
|
|
469
491
|
|
|
470
492
|
Args:
|
|
471
493
|
key: Configuration key to delete
|
|
494
|
+
environment: Environment identifier (REQUIRED)
|
|
472
495
|
|
|
473
496
|
Returns:
|
|
474
497
|
Dictionary with success message
|
|
475
498
|
|
|
476
499
|
Raises:
|
|
477
|
-
ConfigurationNotFoundError: If configuration key does not exist
|
|
478
|
-
ValueError: If key is invalid
|
|
500
|
+
ConfigurationNotFoundError: If configuration (key, environment) does not exist
|
|
501
|
+
ValueError: If key or environment is invalid
|
|
479
502
|
|
|
480
503
|
Example:
|
|
481
|
-
>>>
|
|
504
|
+
>>> # Delete from staging only
|
|
505
|
+
>>> result = client.delete("database", "staging")
|
|
506
|
+
>>> # Production and development environments remain untouched
|
|
482
507
|
>>> print(result["message"])
|
|
483
508
|
Configuration 'database' deleted successfully
|
|
484
509
|
"""
|
|
485
510
|
if not key or not isinstance(key, str):
|
|
486
511
|
raise ValueError("Key must be a non-empty string")
|
|
512
|
+
if not environment or not isinstance(environment, str):
|
|
513
|
+
raise ValueError("Environment is required and must be a non-empty string")
|
|
487
514
|
|
|
488
|
-
|
|
515
|
+
params = {"environment": environment}
|
|
516
|
+
return self._make_request("DELETE", f"/configs/{key}", params=params)
|
|
489
517
|
|
|
490
518
|
def list_all(self, category: Optional[str] = None, environment: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
491
519
|
"""
|
|
@@ -503,10 +531,10 @@ class OpenSecureConfClient:
|
|
|
503
531
|
Example:
|
|
504
532
|
>>> # List all
|
|
505
533
|
>>> configs = client.list_all()
|
|
506
|
-
>>> # Filter by category
|
|
507
|
-
>>> prod_configs = client.list_all(category="production")
|
|
508
534
|
>>> # Filter by environment
|
|
509
|
-
>>>
|
|
535
|
+
>>> prod_configs = client.list_all(environment="production")
|
|
536
|
+
>>> # Filter by category
|
|
537
|
+
>>> db_configs = client.list_all(category="database")
|
|
510
538
|
>>> # Filter by both
|
|
511
539
|
>>> configs = client.list_all(category="database", environment="production")
|
|
512
540
|
"""
|
|
@@ -532,7 +560,7 @@ class OpenSecureConfClient:
|
|
|
532
560
|
|
|
533
561
|
Args:
|
|
534
562
|
configs: List of configuration dictionaries with 'key', 'value',
|
|
535
|
-
and optional 'category'
|
|
563
|
+
'environment' (REQUIRED), and optional 'category'
|
|
536
564
|
Value can be dict, str, int, bool, or list
|
|
537
565
|
ignore_errors: If True, continue on errors and return partial results
|
|
538
566
|
|
|
@@ -540,17 +568,17 @@ class OpenSecureConfClient:
|
|
|
540
568
|
List of created configuration dictionaries
|
|
541
569
|
|
|
542
570
|
Raises:
|
|
543
|
-
ValueError: If configs format is invalid
|
|
571
|
+
ValueError: If configs format is invalid or environment is missing
|
|
544
572
|
OpenSecureConfError: If creation fails and ignore_errors is False
|
|
545
573
|
|
|
546
574
|
Example:
|
|
547
575
|
>>> configs = [
|
|
548
|
-
... {"key": "
|
|
549
|
-
... "
|
|
576
|
+
... {"key": "db", "value": {"host": "localhost"},
|
|
577
|
+
... "environment": "production", "category": "config"},
|
|
578
|
+
... {"key": "db", "value": {"host": "localhost"},
|
|
579
|
+
... "environment": "staging", "category": "config"},
|
|
550
580
|
... {"key": "token", "value": "secret-123",
|
|
551
|
-
... "
|
|
552
|
-
... {"key": "retries", "value": 3,
|
|
553
|
-
... "category": "config", "environment": "production"}
|
|
581
|
+
... "environment": "production", "category": "auth"}
|
|
554
582
|
... ]
|
|
555
583
|
>>> results = client.bulk_create(configs)
|
|
556
584
|
>>> print(f"Created {len(results)} configurations")
|
|
@@ -566,20 +594,28 @@ class OpenSecureConfClient:
|
|
|
566
594
|
raise ValueError(f"Config at index {i} must be a dictionary")
|
|
567
595
|
if "key" not in config or "value" not in config:
|
|
568
596
|
raise ValueError(f"Config at index {i} missing required 'key' or 'value'")
|
|
597
|
+
if "environment" not in config:
|
|
598
|
+
raise ValueError(f"Config at index {i} missing required 'environment'")
|
|
569
599
|
|
|
570
600
|
try:
|
|
571
601
|
result = self.create(
|
|
572
602
|
key=config["key"],
|
|
573
603
|
value=config["value"],
|
|
574
|
-
|
|
575
|
-
|
|
604
|
+
environment=config["environment"],
|
|
605
|
+
category=config.get("category")
|
|
576
606
|
)
|
|
577
607
|
results.append(result)
|
|
578
|
-
self.logger.info(
|
|
608
|
+
self.logger.info(
|
|
609
|
+
f"Bulk create: created '{config['key']}' in '{config['environment']}'"
|
|
610
|
+
)
|
|
579
611
|
except Exception as e:
|
|
580
|
-
error_msg = f"Failed to create '{config['key']}': {str(e)}"
|
|
612
|
+
error_msg = f"Failed to create '{config['key']}' in '{config['environment']}': {str(e)}"
|
|
581
613
|
self.logger.error(error_msg)
|
|
582
|
-
errors.append({
|
|
614
|
+
errors.append({
|
|
615
|
+
"key": config["key"],
|
|
616
|
+
"environment": config["environment"],
|
|
617
|
+
"error": str(e)
|
|
618
|
+
})
|
|
583
619
|
if not ignore_errors:
|
|
584
620
|
raise OpenSecureConfError(error_msg) from e
|
|
585
621
|
|
|
@@ -590,41 +626,66 @@ class OpenSecureConfClient:
|
|
|
590
626
|
|
|
591
627
|
def bulk_read(
|
|
592
628
|
self,
|
|
593
|
-
|
|
629
|
+
items: List[Dict[str, str]],
|
|
594
630
|
ignore_errors: bool = False
|
|
595
631
|
) -> List[Dict[str, Any]]:
|
|
596
632
|
"""
|
|
597
633
|
Read multiple configurations in batch.
|
|
598
634
|
|
|
599
635
|
Args:
|
|
600
|
-
|
|
636
|
+
items: List of dictionaries with 'key' and 'environment' fields
|
|
601
637
|
ignore_errors: If True, skip missing keys and return partial results
|
|
602
638
|
|
|
603
639
|
Returns:
|
|
604
640
|
List of configuration dictionaries
|
|
605
641
|
|
|
642
|
+
Raises:
|
|
643
|
+
ValueError: If items format is invalid
|
|
644
|
+
|
|
606
645
|
Example:
|
|
607
|
-
>>>
|
|
646
|
+
>>> items = [
|
|
647
|
+
... {"key": "database", "environment": "production"},
|
|
648
|
+
... {"key": "database", "environment": "staging"},
|
|
649
|
+
... {"key": "api_token", "environment": "production"}
|
|
650
|
+
... ]
|
|
651
|
+
>>> configs = client.bulk_read(items)
|
|
608
652
|
>>> print(f"Retrieved {len(configs)} configurations")
|
|
609
653
|
"""
|
|
610
|
-
if not isinstance(
|
|
611
|
-
raise ValueError("
|
|
654
|
+
if not isinstance(items, list):
|
|
655
|
+
raise ValueError("items must be a list")
|
|
612
656
|
|
|
613
657
|
results = []
|
|
614
658
|
errors = []
|
|
615
659
|
|
|
616
|
-
for
|
|
660
|
+
for i, item in enumerate(items):
|
|
661
|
+
if not isinstance(item, dict):
|
|
662
|
+
raise ValueError(f"Item at index {i} must be a dictionary")
|
|
663
|
+
if "key" not in item or "environment" not in item:
|
|
664
|
+
raise ValueError(f"Item at index {i} missing required 'key' or 'environment'")
|
|
665
|
+
|
|
617
666
|
try:
|
|
618
|
-
result = self.read(key)
|
|
667
|
+
result = self.read(item["key"], item["environment"])
|
|
619
668
|
results.append(result)
|
|
620
669
|
except ConfigurationNotFoundError as e:
|
|
621
|
-
self.logger.warning(
|
|
622
|
-
|
|
670
|
+
self.logger.warning(
|
|
671
|
+
f"Bulk read: key '{item['key']}' not found in '{item['environment']}'"
|
|
672
|
+
)
|
|
673
|
+
errors.append({
|
|
674
|
+
"key": item["key"],
|
|
675
|
+
"environment": item["environment"],
|
|
676
|
+
"error": str(e)
|
|
677
|
+
})
|
|
623
678
|
if not ignore_errors:
|
|
624
679
|
raise
|
|
625
680
|
except Exception as e:
|
|
626
|
-
self.logger.error(
|
|
627
|
-
|
|
681
|
+
self.logger.error(
|
|
682
|
+
f"Bulk read: failed to read '{item['key']}' from '{item['environment']}': {str(e)}"
|
|
683
|
+
)
|
|
684
|
+
errors.append({
|
|
685
|
+
"key": item["key"],
|
|
686
|
+
"environment": item["environment"],
|
|
687
|
+
"error": str(e)
|
|
688
|
+
})
|
|
628
689
|
if not ignore_errors:
|
|
629
690
|
raise
|
|
630
691
|
|
|
@@ -632,37 +693,61 @@ class OpenSecureConfClient:
|
|
|
632
693
|
|
|
633
694
|
def bulk_delete(
|
|
634
695
|
self,
|
|
635
|
-
|
|
696
|
+
items: List[Dict[str, str]],
|
|
636
697
|
ignore_errors: bool = False
|
|
637
698
|
) -> Dict[str, Any]:
|
|
638
699
|
"""
|
|
639
700
|
Delete multiple configurations in batch.
|
|
640
701
|
|
|
641
702
|
Args:
|
|
642
|
-
|
|
703
|
+
items: List of dictionaries with 'key' and 'environment' fields
|
|
643
704
|
ignore_errors: If True, continue on errors
|
|
644
705
|
|
|
645
706
|
Returns:
|
|
646
|
-
Dictionary with summary: {
|
|
707
|
+
Dictionary with summary: {
|
|
708
|
+
"deleted": [{"key": "...", "environment": "..."}],
|
|
709
|
+
"failed": [{"key": "...", "environment": "...", "error": "..."}]
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
Raises:
|
|
713
|
+
ValueError: If items format is invalid
|
|
647
714
|
|
|
648
715
|
Example:
|
|
649
|
-
>>>
|
|
716
|
+
>>> items = [
|
|
717
|
+
... {"key": "temp1", "environment": "staging"},
|
|
718
|
+
... {"key": "temp2", "environment": "staging"},
|
|
719
|
+
... {"key": "temp3", "environment": "development"}
|
|
720
|
+
... ]
|
|
721
|
+
>>> result = client.bulk_delete(items)
|
|
650
722
|
>>> print(f"Deleted: {len(result['deleted'])}, Failed: {len(result['failed'])}")
|
|
651
723
|
"""
|
|
652
|
-
if not isinstance(
|
|
653
|
-
raise ValueError("
|
|
724
|
+
if not isinstance(items, list):
|
|
725
|
+
raise ValueError("items must be a list")
|
|
654
726
|
|
|
655
727
|
deleted = []
|
|
656
728
|
failed = []
|
|
657
729
|
|
|
658
|
-
for
|
|
730
|
+
for i, item in enumerate(items):
|
|
731
|
+
if not isinstance(item, dict):
|
|
732
|
+
raise ValueError(f"Item at index {i} must be a dictionary")
|
|
733
|
+
if "key" not in item or "environment" not in item:
|
|
734
|
+
raise ValueError(f"Item at index {i} missing required 'key' or 'environment'")
|
|
735
|
+
|
|
659
736
|
try:
|
|
660
|
-
self.delete(key)
|
|
661
|
-
deleted.append(key)
|
|
662
|
-
self.logger.info(
|
|
737
|
+
self.delete(item["key"], item["environment"])
|
|
738
|
+
deleted.append({"key": item["key"], "environment": item["environment"]})
|
|
739
|
+
self.logger.info(
|
|
740
|
+
f"Bulk delete: deleted '{item['key']}' from '{item['environment']}'"
|
|
741
|
+
)
|
|
663
742
|
except Exception as e:
|
|
664
|
-
self.logger.error(
|
|
665
|
-
|
|
743
|
+
self.logger.error(
|
|
744
|
+
f"Bulk delete: failed to delete '{item['key']}' from '{item['environment']}': {str(e)}"
|
|
745
|
+
)
|
|
746
|
+
failed.append({
|
|
747
|
+
"key": item["key"],
|
|
748
|
+
"environment": item["environment"],
|
|
749
|
+
"error": str(e)
|
|
750
|
+
})
|
|
666
751
|
if not ignore_errors:
|
|
667
752
|
raise
|
|
668
753
|
|
|
@@ -672,22 +757,25 @@ class OpenSecureConfClient:
|
|
|
672
757
|
# UTILITY METHODS
|
|
673
758
|
# ========================================================================
|
|
674
759
|
|
|
675
|
-
def exists(self, key: str) -> bool:
|
|
760
|
+
def exists(self, key: str, environment: str) -> bool:
|
|
676
761
|
"""
|
|
677
|
-
Check if a configuration key exists.
|
|
762
|
+
Check if a configuration key exists in specific environment.
|
|
678
763
|
|
|
679
764
|
Args:
|
|
680
765
|
key: Configuration key to check
|
|
766
|
+
environment: Environment identifier (REQUIRED)
|
|
681
767
|
|
|
682
768
|
Returns:
|
|
683
|
-
True if key exists, False otherwise
|
|
769
|
+
True if key exists in the specified environment, False otherwise
|
|
684
770
|
|
|
685
771
|
Example:
|
|
686
|
-
>>> if client.exists("database"):
|
|
687
|
-
... print("Configuration exists")
|
|
772
|
+
>>> if client.exists("database", "production"):
|
|
773
|
+
... print("Configuration exists in production")
|
|
774
|
+
>>> if not client.exists("database", "development"):
|
|
775
|
+
... print("Configuration does not exist in development")
|
|
688
776
|
"""
|
|
689
777
|
try:
|
|
690
|
-
self.read(key)
|
|
778
|
+
self.read(key, environment)
|
|
691
779
|
return True
|
|
692
780
|
except ConfigurationNotFoundError:
|
|
693
781
|
return False
|
|
@@ -695,6 +783,7 @@ class OpenSecureConfClient:
|
|
|
695
783
|
def get_or_default(
|
|
696
784
|
self,
|
|
697
785
|
key: str,
|
|
786
|
+
environment: str,
|
|
698
787
|
default: Union[Dict[str, Any], str, int, bool, list]
|
|
699
788
|
) -> Dict[str, Any]:
|
|
700
789
|
"""
|
|
@@ -702,6 +791,7 @@ class OpenSecureConfClient:
|
|
|
702
791
|
|
|
703
792
|
Args:
|
|
704
793
|
key: Configuration key to retrieve
|
|
794
|
+
environment: Environment identifier (REQUIRED)
|
|
705
795
|
default: Default value to return if key not found (any supported type)
|
|
706
796
|
|
|
707
797
|
Returns:
|
|
@@ -709,14 +799,21 @@ class OpenSecureConfClient:
|
|
|
709
799
|
|
|
710
800
|
Example:
|
|
711
801
|
>>> # Dict default
|
|
712
|
-
>>> config = client.get_or_default(
|
|
802
|
+
>>> config = client.get_or_default(
|
|
803
|
+
... "database", "production", {"host": "localhost", "port": 5432}
|
|
804
|
+
... )
|
|
713
805
|
>>> # String default
|
|
714
|
-
>>> config = client.get_or_default("token", "default-token")
|
|
806
|
+
>>> config = client.get_or_default("token", "staging", "default-token")
|
|
715
807
|
"""
|
|
716
808
|
try:
|
|
717
|
-
return self.read(key)
|
|
809
|
+
return self.read(key, environment)
|
|
718
810
|
except ConfigurationNotFoundError:
|
|
719
|
-
return {
|
|
811
|
+
return {
|
|
812
|
+
"key": key,
|
|
813
|
+
"value": default,
|
|
814
|
+
"environment": environment,
|
|
815
|
+
"category": None
|
|
816
|
+
}
|
|
720
817
|
|
|
721
818
|
def count(self, category: Optional[str] = None, environment: Optional[str] = None) -> int:
|
|
722
819
|
"""
|
|
@@ -766,7 +863,7 @@ class OpenSecureConfClient:
|
|
|
766
863
|
Example:
|
|
767
864
|
>>> environments = client.list_environments()
|
|
768
865
|
>>> print(f"Environments: {', '.join(environments)}")
|
|
769
|
-
Environments: production, staging
|
|
866
|
+
Environments: development, production, staging
|
|
770
867
|
"""
|
|
771
868
|
configs = self.list_all()
|
|
772
869
|
environments = set()
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
opensecureconf_client.py,sha256=YReSxnFPu_7CDhqjBxcOTrJxYaWMTihiyECnWVmVgCw,30028
|
|
2
|
-
opensecureconf_client-2.3.1.dist-info/licenses/LICENSE,sha256=mvMdzinneV_-L01ddrHOBgbutNS8tjT1m7loT7VTWbI,1073
|
|
3
|
-
opensecureconf_client-2.3.1.dist-info/METADATA,sha256=tYJjAbvyIAKchDQ2BUQv4KyrIv-whJCAZpxPDkYGr78,33865
|
|
4
|
-
opensecureconf_client-2.3.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
-
opensecureconf_client-2.3.1.dist-info/top_level.txt,sha256=J7NP3hD92OUdqseJLlbzgPuG_ovqkURRyw7iBJJeDVE,22
|
|
6
|
-
opensecureconf_client-2.3.1.dist-info/RECORD,,
|
|
File without changes
|
{opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|