opensecureconf-client 2.0.2__py3-none-any.whl → 2.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensecureconf-client
3
- Version: 2.0.2
3
+ Version: 2.3.1
4
4
  Summary: Python client library for OpenSecureConf encrypted configuration management API with clustering support
5
5
  Author-email: Alessandro Pioli <alessandro.pioli+apioli-pypi@gmail.com>
6
6
  Maintainer-email: Alessandro Pioli <alessandro.pioli+apioli-pypi@gmail.com>
@@ -0,0 +1,6 @@
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
opensecureconf_client.py CHANGED
@@ -12,9 +12,11 @@ Enhanced Features:
12
12
  - Batch operations
13
13
  - Enhanced input validation
14
14
  - Health check utilities
15
+ - Support for multiple value types (dict, str, int, bool, list)
16
+ - Category and environment support
15
17
  """
16
18
 
17
- from typing import Any, Dict, List, Optional
19
+ from typing import Any, Dict, List, Optional, Union
18
20
  import logging
19
21
  import time
20
22
  import requests
@@ -22,7 +24,6 @@ from requests.adapters import HTTPAdapter
22
24
  from requests.exceptions import Timeout, RequestException
23
25
  from urllib3.util.retry import Retry
24
26
 
25
-
26
27
  # ============================================================================
27
28
  # EXCEPTIONS
28
29
  # ============================================================================
@@ -30,23 +31,18 @@ from urllib3.util.retry import Retry
30
31
  class OpenSecureConfError(Exception):
31
32
  """Base exception for OpenSecureConf client errors."""
32
33
 
33
-
34
34
  class AuthenticationError(OpenSecureConfError):
35
35
  """Raised when authentication fails (invalid or missing user key)."""
36
36
 
37
-
38
37
  class ConfigurationNotFoundError(OpenSecureConfError):
39
38
  """Raised when a requested configuration key does not exist."""
40
39
 
41
-
42
40
  class ConfigurationExistsError(OpenSecureConfError):
43
41
  """Raised when attempting to create a configuration that already exists."""
44
42
 
45
-
46
43
  class ClusterError(OpenSecureConfError):
47
44
  """Raised when cluster operations fail."""
48
45
 
49
-
50
46
  # ============================================================================
51
47
  # CLIENT
52
48
  # ============================================================================
@@ -73,9 +69,12 @@ class OpenSecureConfClient:
73
69
  ... enable_retry=True,
74
70
  ... log_level="INFO"
75
71
  ... )
76
- >>> config = client.create("database", {"host": "localhost", "port": 5432})
77
- >>> print(config["value"])
78
- {'host': 'localhost', 'port': 5432}
72
+ >>> # Dict value with category and environment
73
+ >>> config = client.create("database", {"host": "localhost", "port": 5432},
74
+ ... category="config", environment="production")
75
+ >>> # String value
76
+ >>> config = client.create("api_token", "secret-token-123",
77
+ ... category="auth", environment="staging")
79
78
  """
80
79
 
81
80
  def __init__(
@@ -171,12 +170,10 @@ class OpenSecureConfClient:
171
170
  "x-user-key": self.user_key,
172
171
  "Content-Type": "application/json"
173
172
  }
174
-
175
173
  if self.api_key:
176
174
  headers["X-API-Key"] = self.api_key
177
175
 
178
176
  self._session.headers.update(headers)
179
-
180
177
  self.logger.info(f"Client initialized for {self.base_url}")
181
178
 
182
179
  def _make_request(self, method: str, endpoint: str, **kwargs) -> Any:
@@ -208,7 +205,6 @@ class OpenSecureConfClient:
208
205
  try:
209
206
  response = self._session.request(method, url, **kwargs)
210
207
  duration = time.time() - start_time
211
-
212
208
  self.logger.info(
213
209
  f"{method} {endpoint} - Status: {response.status_code} - Duration: {duration:.3f}s"
214
210
  )
@@ -295,7 +291,7 @@ class OpenSecureConfClient:
295
291
  Example:
296
292
  >>> info = client.get_service_info()
297
293
  >>> print(info["version"])
298
- 2.1.0
294
+ 2.2.0
299
295
  """
300
296
  return self._make_request("GET", "/")
301
297
 
@@ -350,18 +346,20 @@ class OpenSecureConfClient:
350
346
  # ========================================================================
351
347
 
352
348
  def create(
353
- self,
354
- key: str,
355
- value: Dict[str, Any],
356
- category: Optional[str] = None
349
+ self,
350
+ key: str,
351
+ value: Union[Dict[str, Any], str, int, bool, list],
352
+ category: Optional[str] = None,
353
+ environment: Optional[str] = None
357
354
  ) -> Dict[str, Any]:
358
355
  """
359
356
  Create a new encrypted configuration entry.
360
357
 
361
358
  Args:
362
359
  key: Unique configuration key (1-255 characters)
363
- value: Configuration data as dictionary (will be encrypted)
360
+ value: Configuration data (dict, string, int, bool, or list - will be encrypted)
364
361
  category: Optional category for grouping (max 100 characters)
362
+ environment: Optional environment identifier (max 100 characters)
365
363
 
366
364
  Returns:
367
365
  Dictionary containing the created configuration with fields:
@@ -369,29 +367,35 @@ class OpenSecureConfClient:
369
367
  - key: Configuration key
370
368
  - value: Configuration value (decrypted)
371
369
  - category: Configuration category (if set)
370
+ - environment: Environment identifier (if set)
372
371
 
373
372
  Raises:
374
373
  ConfigurationExistsError: If configuration key already exists
375
- ValueError: If key or value is invalid
374
+ ValueError: If key is invalid
376
375
 
377
376
  Example:
378
- >>> config = client.create(
379
- ... key="database",
380
- ... value={"host": "localhost", "port": 5432},
381
- ... category="production"
382
- ... )
377
+ >>> # Dict value with category and environment
378
+ >>> config = client.create("database", {"host": "localhost", "port": 5432},
379
+ ... category="config", environment="production")
380
+ >>> # String value
381
+ >>> config = client.create("api_token", "secret-123",
382
+ ... category="auth", environment="staging")
383
+ >>> # Integer value
384
+ >>> config = client.create("max_retries", 3, environment="production")
385
+ >>> # Boolean value
386
+ >>> config = client.create("debug", False, category="settings", environment="dev")
383
387
  """
384
388
  # Enhanced validation
385
389
  if not key or not isinstance(key, str):
386
390
  raise ValueError("Key must be a non-empty string")
387
391
  if len(key) > 255:
388
392
  raise ValueError("Key must be between 1 and 255 characters")
389
- if not isinstance(value, dict):
390
- raise ValueError("Value must be a dictionary")
391
393
  if category and len(category) > 100:
392
394
  raise ValueError("Category must be max 100 characters")
395
+ if environment and len(environment) > 100:
396
+ raise ValueError("Environment must be max 100 characters")
393
397
 
394
- payload = {"key": key, "value": value, "category": category}
398
+ payload = {"key": key, "value": value, "category": category, "environment": environment}
395
399
  return self._make_request("POST", "/configs", json=payload)
396
400
 
397
401
  def read(self, key: str) -> Dict[str, Any]:
@@ -403,6 +407,7 @@ class OpenSecureConfClient:
403
407
 
404
408
  Returns:
405
409
  Dictionary containing the configuration with decrypted value
410
+ The value can be dict, str, int, bool, or list depending on what was stored
406
411
 
407
412
  Raises:
408
413
  ConfigurationNotFoundError: If configuration key does not exist
@@ -410,8 +415,8 @@ class OpenSecureConfClient:
410
415
 
411
416
  Example:
412
417
  >>> config = client.read("database")
413
- >>> print(config["value"]["host"])
414
- localhost
418
+ >>> print(config["value"]) # Could be any supported type
419
+ >>> print(config["environment"]) # e.g., "production"
415
420
  """
416
421
  if not key or not isinstance(key, str):
417
422
  raise ValueError("Key must be a non-empty string")
@@ -419,40 +424,43 @@ class OpenSecureConfClient:
419
424
  return self._make_request("GET", f"/configs/{key}")
420
425
 
421
426
  def update(
422
- self,
423
- key: str,
424
- value: Dict[str, Any],
425
- category: Optional[str] = None
427
+ self,
428
+ key: str,
429
+ value: Union[Dict[str, Any], str, int, bool, list],
430
+ category: Optional[str] = None,
431
+ environment: Optional[str] = None
426
432
  ) -> Dict[str, Any]:
427
433
  """
428
434
  Update an existing configuration entry with new encrypted value.
429
435
 
430
436
  Args:
431
437
  key: Configuration key to update
432
- value: New configuration data as dictionary (will be encrypted)
438
+ value: New configuration data (dict, string, int, bool, or list - will be encrypted)
433
439
  category: Optional new category
440
+ environment: Optional new environment
434
441
 
435
442
  Returns:
436
443
  Dictionary containing the updated configuration with decrypted value
437
444
 
438
445
  Raises:
439
446
  ConfigurationNotFoundError: If configuration key does not exist
440
- ValueError: If parameters are invalid
447
+ ValueError: If key is invalid
441
448
 
442
449
  Example:
443
- >>> config = client.update(
444
- ... key="database",
445
- ... value={"host": "db.example.com", "port": 5432}
446
- ... )
450
+ >>> # Update with dict
451
+ >>> config = client.update("database", {"host": "db.example.com", "port": 5432},
452
+ ... environment="production")
453
+ >>> # Update with string and change environment
454
+ >>> config = client.update("api_token", "new-token-456", environment="staging")
447
455
  """
448
456
  if not key or not isinstance(key, str):
449
457
  raise ValueError("Key must be a non-empty string")
450
- if not isinstance(value, dict):
451
- raise ValueError("Value must be a dictionary")
452
458
  if category and len(category) > 100:
453
459
  raise ValueError("Category must be max 100 characters")
460
+ if environment and len(environment) > 100:
461
+ raise ValueError("Environment must be max 100 characters")
454
462
 
455
- payload = {"value": value, "category": category}
463
+ payload = {"value": value, "category": category, "environment": environment}
456
464
  return self._make_request("PUT", f"/configs/{key}", json=payload)
457
465
 
458
466
  def delete(self, key: str) -> Dict[str, str]:
@@ -479,23 +487,35 @@ class OpenSecureConfClient:
479
487
 
480
488
  return self._make_request("DELETE", f"/configs/{key}")
481
489
 
482
- def list_all(self, category: Optional[str] = None) -> List[Dict[str, Any]]:
490
+ def list_all(self, category: Optional[str] = None, environment: Optional[str] = None) -> List[Dict[str, Any]]:
483
491
  """
484
- List all configurations with optional category filter.
492
+ List all configurations with optional category and environment filters.
485
493
  All values are automatically decrypted.
486
494
 
487
495
  Args:
488
496
  category: Optional filter by category
497
+ environment: Optional filter by environment
489
498
 
490
499
  Returns:
491
500
  List of configuration dictionaries with decrypted values
501
+ Each value can be dict, str, int, bool, or list
492
502
 
493
503
  Example:
494
- >>> configs = client.list_all(category="production")
495
- >>> for config in configs:
496
- ... print(f"{config['key']}: {config['value']}")
497
- """
498
- params = {"category": category} if category else {}
504
+ >>> # List all
505
+ >>> configs = client.list_all()
506
+ >>> # Filter by category
507
+ >>> prod_configs = client.list_all(category="production")
508
+ >>> # Filter by environment
509
+ >>> staging_configs = client.list_all(environment="staging")
510
+ >>> # Filter by both
511
+ >>> configs = client.list_all(category="database", environment="production")
512
+ """
513
+ params = {}
514
+ if category:
515
+ params["category"] = category
516
+ if environment:
517
+ params["environment"] = environment
518
+
499
519
  return self._make_request("GET", "/configs", params=params)
500
520
 
501
521
  # ========================================================================
@@ -503,15 +523,17 @@ class OpenSecureConfClient:
503
523
  # ========================================================================
504
524
 
505
525
  def bulk_create(
506
- self,
507
- configs: List[Dict[str, Any]],
526
+ self,
527
+ configs: List[Dict[str, Any]],
508
528
  ignore_errors: bool = False
509
529
  ) -> List[Dict[str, Any]]:
510
530
  """
511
531
  Create multiple configurations in batch.
512
532
 
513
533
  Args:
514
- configs: List of configuration dictionaries with 'key', 'value', and optional 'category'
534
+ configs: List of configuration dictionaries with 'key', 'value',
535
+ and optional 'category' and 'environment'
536
+ Value can be dict, str, int, bool, or list
515
537
  ignore_errors: If True, continue on errors and return partial results
516
538
 
517
539
  Returns:
@@ -523,8 +545,12 @@ class OpenSecureConfClient:
523
545
 
524
546
  Example:
525
547
  >>> configs = [
526
- ... {"key": "db1", "value": {"host": "localhost"}, "category": "prod"},
527
- ... {"key": "db2", "value": {"host": "remote"}, "category": "prod"}
548
+ ... {"key": "db1", "value": {"host": "localhost"},
549
+ ... "category": "db", "environment": "production"},
550
+ ... {"key": "token", "value": "secret-123",
551
+ ... "category": "auth", "environment": "staging"},
552
+ ... {"key": "retries", "value": 3,
553
+ ... "category": "config", "environment": "production"}
528
554
  ... ]
529
555
  >>> results = client.bulk_create(configs)
530
556
  >>> print(f"Created {len(results)} configurations")
@@ -545,7 +571,8 @@ class OpenSecureConfClient:
545
571
  result = self.create(
546
572
  key=config["key"],
547
573
  value=config["value"],
548
- category=config.get("category")
574
+ category=config.get("category"),
575
+ environment=config.get("environment")
549
576
  )
550
577
  results.append(result)
551
578
  self.logger.info(f"Bulk create: created '{config['key']}'")
@@ -562,8 +589,8 @@ class OpenSecureConfClient:
562
589
  return results
563
590
 
564
591
  def bulk_read(
565
- self,
566
- keys: List[str],
592
+ self,
593
+ keys: List[str],
567
594
  ignore_errors: bool = False
568
595
  ) -> List[Dict[str, Any]]:
569
596
  """
@@ -577,7 +604,7 @@ class OpenSecureConfClient:
577
604
  List of configuration dictionaries
578
605
 
579
606
  Example:
580
- >>> configs = client.bulk_read(["db1", "db2", "api"])
607
+ >>> configs = client.bulk_read(["db1", "token", "retries"])
581
608
  >>> print(f"Retrieved {len(configs)} configurations")
582
609
  """
583
610
  if not isinstance(keys, list):
@@ -604,8 +631,8 @@ class OpenSecureConfClient:
604
631
  return results
605
632
 
606
633
  def bulk_delete(
607
- self,
608
- keys: List[str],
634
+ self,
635
+ keys: List[str],
609
636
  ignore_errors: bool = False
610
637
  ) -> Dict[str, Any]:
611
638
  """
@@ -666,46 +693,48 @@ class OpenSecureConfClient:
666
693
  return False
667
694
 
668
695
  def get_or_default(
669
- self,
670
- key: str,
671
- default: Dict[str, Any]
696
+ self,
697
+ key: str,
698
+ default: Union[Dict[str, Any], str, int, bool, list]
672
699
  ) -> Dict[str, Any]:
673
700
  """
674
701
  Get configuration value or return default if not found.
675
702
 
676
703
  Args:
677
704
  key: Configuration key to retrieve
678
- default: Default value to return if key not found
705
+ default: Default value to return if key not found (any supported type)
679
706
 
680
707
  Returns:
681
- Configuration dictionary or default value
708
+ Configuration dictionary or default value wrapped in dict format
682
709
 
683
710
  Example:
684
- >>> config = client.get_or_default(
685
- ... "database",
686
- ... {"host": "localhost", "port": 5432}
687
- ... )
711
+ >>> # Dict default
712
+ >>> config = client.get_or_default("database", {"host": "localhost", "port": 5432})
713
+ >>> # String default
714
+ >>> config = client.get_or_default("token", "default-token")
688
715
  """
689
716
  try:
690
717
  return self.read(key)
691
718
  except ConfigurationNotFoundError:
692
- return {"key": key, "value": default, "category": None}
719
+ return {"key": key, "value": default, "category": None, "environment": None}
693
720
 
694
- def count(self, category: Optional[str] = None) -> int:
721
+ def count(self, category: Optional[str] = None, environment: Optional[str] = None) -> int:
695
722
  """
696
- Count total configurations, optionally filtered by category.
723
+ Count total configurations, optionally filtered by category and/or environment.
697
724
 
698
725
  Args:
699
726
  category: Optional category filter
727
+ environment: Optional environment filter
700
728
 
701
729
  Returns:
702
730
  Number of configurations
703
731
 
704
732
  Example:
705
733
  >>> total = client.count()
706
- >>> prod_count = client.count(category="production")
734
+ >>> prod_count = client.count(environment="production")
735
+ >>> db_prod_count = client.count(category="database", environment="production")
707
736
  """
708
- configs = self.list_all(category=category)
737
+ configs = self.list_all(category=category, environment=environment)
709
738
  return len(configs)
710
739
 
711
740
  def list_categories(self) -> List[str]:
@@ -727,6 +756,26 @@ class OpenSecureConfClient:
727
756
  categories.add(cat)
728
757
  return sorted(list(categories))
729
758
 
759
+ def list_environments(self) -> List[str]:
760
+ """
761
+ Get list of all unique environments.
762
+
763
+ Returns:
764
+ List of environment names
765
+
766
+ Example:
767
+ >>> environments = client.list_environments()
768
+ >>> print(f"Environments: {', '.join(environments)}")
769
+ Environments: production, staging, development
770
+ """
771
+ configs = self.list_all()
772
+ environments = set()
773
+ for config in configs:
774
+ env = config.get("environment")
775
+ if env:
776
+ environments.add(env)
777
+ return sorted(list(environments))
778
+
730
779
  # ========================================================================
731
780
  # SESSION MANAGEMENT
732
781
  # ========================================================================
@@ -1,6 +0,0 @@
1
- opensecureconf_client.py,sha256=hb7GGTHoRjMdK0KACtYkZUUlqbxPHVVidLDZkFqMB9o,26560
2
- opensecureconf_client-2.0.2.dist-info/licenses/LICENSE,sha256=mvMdzinneV_-L01ddrHOBgbutNS8tjT1m7loT7VTWbI,1073
3
- opensecureconf_client-2.0.2.dist-info/METADATA,sha256=2xWbal_tTrf3yaYysEqgsedMmqCnhqcDl-kSj0Zyprs,33865
4
- opensecureconf_client-2.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- opensecureconf_client-2.0.2.dist-info/top_level.txt,sha256=J7NP3hD92OUdqseJLlbzgPuG_ovqkURRyw7iBJJeDVE,22
6
- opensecureconf_client-2.0.2.dist-info/RECORD,,