opensecureconf-client 2.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensecureconf-client
3
- Version: 2.3.0
3
+ Version: 3.0.0
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>
@@ -46,7 +46,7 @@ Dynamic: license-file
46
46
  [![Python](https://img.shields.io/pypi/pyversions/opensecureconf-client.svg)](https://pypi.org/project/opensecureconf-client/)
47
47
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
48
48
 
49
- A powerful Python client library for interacting with the [OpenSecureConf API](https://github.com/lordraw77/OpenSecureConf), providing encrypted configuration management with clustering support, automatic retry logic, and comprehensive monitoring capabilities.
49
+ A powerful Python client library for interacting with the [OpenSecureConf API](https://github.com/lordraw77/OpenSecureConf), providing encrypted configuration management with clustering support, automatic retry logic, multi-environment support, and comprehensive monitoring capabilities.
50
50
 
51
51
  ## 📋 Table of Contents
52
52
 
@@ -54,6 +54,7 @@ A powerful Python client library for interacting with the [OpenSecureConf API](h
54
54
  - [Installation](#installation)
55
55
  - [Quick Start](#quick-start)
56
56
  - [Core Features](#core-features)
57
+ - [Multi-Environment Support](#multi-environment-support)
57
58
  - [Basic CRUD Operations](#basic-crud-operations)
58
59
  - [Cluster Awareness](#cluster-awareness)
59
60
  - [Batch Operations](#batch-operations)
@@ -73,6 +74,7 @@ A powerful Python client library for interacting with the [OpenSecureConf API](h
73
74
 
74
75
  ### Core Capabilities
75
76
  - 🔐 **Encrypted Configuration Management**: Securely store and retrieve encrypted configurations using PBKDF2 + Fernet
77
+ - 🌍 **Multi-Environment Support**: Same configuration key across different environments (production, staging, development)
76
78
  - 🚀 **Simple & Intuitive API**: Clean interface for CRUD operations
77
79
  - 🛡️ **Type-Safe**: Fully typed with comprehensive error handling
78
80
  - 📦 **Lightweight**: Minimal dependencies (only `requests` and `urllib3`)
@@ -123,36 +125,84 @@ client = OpenSecureConfClient(
123
125
  api_key="cluster-secret-key-123" # Optional: if API key authentication is enabled
124
126
  )
125
127
 
126
- # Create a configuration
128
+ # Create a configuration (environment is REQUIRED)
127
129
  config = client.create(
128
130
  key="database",
129
131
  value={"host": "localhost", "port": 5432, "username": "admin", "password": "secret"},
130
- category="production"
132
+ environment="production", # REQUIRED
133
+ category="config"
131
134
  )
132
- print(f"Created: {config['key']}")
135
+ print(f"Created: {config['key']} in {config['environment']}")
133
136
 
134
- # Read a configuration
135
- db_config = client.read("database")
137
+ # Read a configuration (environment is REQUIRED)
138
+ db_config = client.read("database", "production")
136
139
  print(f"Host: {db_config['value']['host']}")
137
140
 
138
- # Update a configuration
141
+ # Update a configuration (environment is REQUIRED)
139
142
  updated = client.update(
140
143
  key="database",
144
+ environment="production", # REQUIRED
141
145
  value={"host": "db.example.com", "port": 5432, "username": "admin", "password": "secret"}
142
146
  )
143
147
 
144
- # List all configurations
145
- configs = client.list_all(category="production")
148
+ # List all production configurations
149
+ configs = client.list_all(environment="production")
146
150
  for cfg in configs:
147
- print(f"- {cfg['key']}: {cfg['category']}")
151
+ print(f"- {cfg['key']}: {cfg['environment']}")
148
152
 
149
- # Delete a configuration
150
- client.delete("database")
153
+ # Delete a configuration (environment is REQUIRED)
154
+ client.delete("database", "production")
151
155
 
152
156
  # Close the client
153
157
  client.close()
154
158
  ```
155
159
 
160
+ ### Multi-Environment Configuration
161
+
162
+ ```python
163
+ from opensecureconf_client import OpenSecureConfClient
164
+
165
+ with OpenSecureConfClient(
166
+ base_url="http://localhost:9000",
167
+ user_key="my-secure-key-min-8-chars"
168
+ ) as client:
169
+ # Create same key in different environments
170
+ prod_db = client.create(
171
+ "database",
172
+ {"host": "db.prod.com", "port": 5432},
173
+ "production",
174
+ "config"
175
+ )
176
+
177
+ staging_db = client.create(
178
+ "database",
179
+ {"host": "db.staging.com", "port": 5432},
180
+ "staging",
181
+ "config"
182
+ )
183
+
184
+ dev_db = client.create(
185
+ "database",
186
+ {"host": "localhost", "port": 5432},
187
+ "development",
188
+ "config"
189
+ )
190
+
191
+ # Read from specific environment
192
+ prod_config = client.read("database", "production")
193
+ staging_config = client.read("database", "staging")
194
+
195
+ print(f"Production: {prod_config['value']['host']}")
196
+ print(f"Staging: {staging_config['value']['host']}")
197
+
198
+ # Update only staging
199
+ client.update("database", "staging", {"host": "db-new.staging.com", "port": 5432})
200
+
201
+ # Delete only development
202
+ client.delete("database", "development")
203
+ # Production and staging remain untouched
204
+ ```
205
+
156
206
  ### Using Context Manager (Recommended)
157
207
 
158
208
  ```python
@@ -163,7 +213,7 @@ with OpenSecureConfClient(
163
213
  user_key="my-secure-key-min-8-chars"
164
214
  ) as client:
165
215
  # Create and use configurations
166
- config = client.create("app", {"version": "1.0.0", "debug": False})
216
+ config = client.create("app", {"version": "1.0.0", "debug": False}, "production")
167
217
  print(config)
168
218
  # Session automatically closed when exiting context
169
219
  ```
@@ -191,33 +241,78 @@ client = OpenSecureConfClient(
191
241
 
192
242
  ## 🎯 Core Features
193
243
 
244
+ ### Multi-Environment Support
245
+
246
+ OpenSecureConf now supports having the same configuration key in different environments, allowing you to maintain separate configurations for production, staging, development, etc.
247
+
248
+ ```python
249
+ # Create same key in multiple environments
250
+ environments = ["production", "staging", "development"]
251
+ configs = {
252
+ "production": {"host": "db.prod.com", "port": 5432, "ssl": True},
253
+ "staging": {"host": "db.staging.com", "port": 5432, "ssl": True},
254
+ "development": {"host": "localhost", "port": 5432, "ssl": False}
255
+ }
256
+
257
+ for env in environments:
258
+ client.create("database", configs[env], env, "config")
259
+
260
+ # Read from specific environment
261
+ prod_db = client.read("database", "production")
262
+ dev_db = client.read("database", "development")
263
+
264
+ print(f"Production uses: {prod_db['value']['host']}")
265
+ print(f"Development uses: {dev_db['value']['host']}")
266
+
267
+ # List all environments
268
+ all_environments = client.list_environments()
269
+ print(f"Available environments: {', '.join(all_environments)}")
270
+
271
+ # List configurations for specific environment
272
+ prod_configs = client.list_all(environment="production")
273
+ print(f"Production has {len(prod_configs)} configurations")
274
+
275
+ # Update only in specific environment
276
+ client.update("database", "staging", {"host": "db-v2.staging.com", "port": 5433})
277
+
278
+ # Delete from specific environment
279
+ client.delete("database", "development")
280
+ # Production and staging remain untouched
281
+ ```
282
+
194
283
  ### Basic CRUD Operations
195
284
 
196
285
  #### Create Configuration
197
286
 
198
287
  ```python
199
- # Create a simple configuration
288
+ # Create a configuration (environment is REQUIRED)
200
289
  config = client.create(
201
290
  key="api_settings",
202
291
  value={"base_url": "https://api.example.com", "timeout": 30, "retries": 3},
203
- category="production"
292
+ environment="production", # REQUIRED
293
+ category="config"
204
294
  )
205
295
 
206
296
  # Create with validation
207
- if not client.exists("api_settings"):
208
- config = client.create("api_settings", {"base_url": "https://api.example.com"})
297
+ if not client.exists("api_settings", "production"):
298
+ config = client.create(
299
+ "api_settings",
300
+ {"base_url": "https://api.example.com"},
301
+ "production"
302
+ )
209
303
  ```
210
304
 
211
305
  #### Read Configuration
212
306
 
213
307
  ```python
214
- # Read a specific configuration
215
- config = client.read("api_settings")
308
+ # Read a specific configuration (environment is REQUIRED)
309
+ config = client.read("api_settings", "production")
216
310
  print(f"API URL: {config['value']['base_url']}")
217
311
 
218
312
  # Safe read with default value
219
313
  config = client.get_or_default(
220
314
  "optional_setting",
315
+ "production",
221
316
  default={"enabled": False, "timeout": 30}
222
317
  )
223
318
  ```
@@ -225,24 +320,25 @@ config = client.get_or_default(
225
320
  #### Update Configuration
226
321
 
227
322
  ```python
228
- # Update existing configuration
323
+ # Update existing configuration (environment is REQUIRED)
229
324
  updated = client.update(
230
325
  key="api_settings",
326
+ environment="production", # REQUIRED, cannot be changed
231
327
  value={"base_url": "https://api.example.com", "timeout": 60, "retries": 5},
232
- category="production"
328
+ category="config"
233
329
  )
234
330
  ```
235
331
 
236
332
  #### Delete Configuration
237
333
 
238
334
  ```python
239
- # Delete a single configuration
240
- result = client.delete("api_settings")
335
+ # Delete a configuration (environment is REQUIRED)
336
+ result = client.delete("api_settings", "production")
241
337
  print(result["message"])
242
338
 
243
339
  # Conditional delete
244
- if client.exists("temporary_config"):
245
- client.delete("temporary_config")
340
+ if client.exists("temporary_config", "staging"):
341
+ client.delete("temporary_config", "staging")
246
342
  ```
247
343
 
248
344
  #### List Configurations
@@ -252,14 +348,20 @@ if client.exists("temporary_config"):
252
348
  all_configs = client.list_all()
253
349
  print(f"Total configurations: {len(all_configs)}")
254
350
 
255
- # List by category
256
- prod_configs = client.list_all(category="production")
351
+ # List by environment
352
+ prod_configs = client.list_all(environment="production")
257
353
  for config in prod_configs:
258
354
  print(f"- {config['key']}: {config['value']}")
259
355
 
356
+ # List by category
357
+ db_configs = client.list_all(category="database")
358
+
359
+ # List by both environment and category
360
+ prod_db_configs = client.list_all(category="database", environment="production")
361
+
260
362
  # Get count
261
363
  total = client.count()
262
- prod_count = client.count(category="production")
364
+ prod_count = client.count(environment="production")
263
365
  print(f"Production configs: {prod_count}/{total}")
264
366
  ```
265
367
 
@@ -293,21 +395,24 @@ Perform multiple operations efficiently:
293
395
  #### Bulk Create
294
396
 
295
397
  ```python
296
- # Create multiple configurations at once
398
+ # Create multiple configurations at once (environment REQUIRED for each)
297
399
  configs_to_create = [
298
400
  {
299
401
  "key": "service1",
300
402
  "value": {"url": "http://service1.local", "timeout": 30},
403
+ "environment": "production", # REQUIRED
301
404
  "category": "microservices"
302
405
  },
303
406
  {
304
- "key": "service2",
305
- "value": {"url": "http://service2.local", "timeout": 60},
407
+ "key": "service1",
408
+ "value": {"url": "http://service1.staging.local", "timeout": 30},
409
+ "environment": "staging", # REQUIRED (same key, different environment)
306
410
  "category": "microservices"
307
411
  },
308
412
  {
309
- "key": "service3",
310
- "value": {"url": "http://service3.local", "timeout": 45},
413
+ "key": "service2",
414
+ "value": {"url": "http://service2.local", "timeout": 60},
415
+ "environment": "production", # REQUIRED
311
416
  "category": "microservices"
312
417
  }
313
418
  ]
@@ -324,35 +429,44 @@ print(f"Created {len(results)} configurations")
324
429
  #### Bulk Read
325
430
 
326
431
  ```python
327
- # Read multiple configurations at once
328
- keys = ["service1", "service2", "service3", "api_settings"]
432
+ # Read multiple configurations at once (environment REQUIRED for each)
433
+ items = [
434
+ {"key": "service1", "environment": "production"},
435
+ {"key": "service1", "environment": "staging"},
436
+ {"key": "service2", "environment": "production"},
437
+ {"key": "api_settings", "environment": "production"}
438
+ ]
329
439
 
330
440
  # Read all (stop on first error)
331
- configs = client.bulk_read(keys)
441
+ configs = client.bulk_read(items)
332
442
 
333
443
  # Read all (skip missing keys)
334
- configs = client.bulk_read(keys, ignore_errors=True)
444
+ configs = client.bulk_read(items, ignore_errors=True)
335
445
  for config in configs:
336
- print(f"{config['key']}: {config['value']['url']}")
446
+ print(f"{config['key']} ({config['environment']}): {config['value']}")
337
447
  ```
338
448
 
339
449
  #### Bulk Delete
340
450
 
341
451
  ```python
342
- # Delete multiple configurations
343
- keys_to_delete = ["temp1", "temp2", "temp3"]
452
+ # Delete multiple configurations (environment REQUIRED for each)
453
+ items_to_delete = [
454
+ {"key": "temp1", "environment": "staging"},
455
+ {"key": "temp2", "environment": "staging"},
456
+ {"key": "temp3", "environment": "development"}
457
+ ]
344
458
 
345
459
  # Delete all (stop on first error)
346
- result = client.bulk_delete(keys_to_delete)
460
+ result = client.bulk_delete(items_to_delete)
347
461
 
348
462
  # Delete all (continue on errors)
349
- result = client.bulk_delete(keys_to_delete, ignore_errors=True)
463
+ result = client.bulk_delete(items_to_delete, ignore_errors=True)
350
464
  print(f"Deleted: {len(result['deleted'])}")
351
465
  print(f"Failed: {len(result['failed'])}")
352
466
 
353
467
  # Inspect failures
354
468
  for failure in result['failed']:
355
- print(f"Failed to delete '{failure['key']}': {failure['error']}")
469
+ print(f"Failed to delete '{failure['key']}' from '{failure['environment']}': {failure['error']}")
356
470
  ```
357
471
 
358
472
  ### Retry Logic
@@ -372,7 +486,7 @@ client = OpenSecureConfClient(
372
486
 
373
487
  # Automatic retry on transient failures (429, 500, 502, 503, 504)
374
488
  try:
375
- config = client.read("my_config")
489
+ config = client.read("my_config", "production")
376
490
  except Exception as e:
377
491
  print(f"Failed after {client.max_retries} retries: {e}")
378
492
  ```
@@ -413,21 +527,22 @@ Convenient helper methods:
413
527
  #### Check Existence
414
528
 
415
529
  ```python
416
- # Check if a key exists
417
- if client.exists("database"):
418
- print("Configuration exists")
419
- config = client.read("database")
530
+ # Check if a key exists in specific environment
531
+ if client.exists("database", "production"):
532
+ print("Configuration exists in production")
533
+ config = client.read("database", "production")
420
534
  else:
421
- print("Configuration does not exist")
422
- config = client.create("database", {"host": "localhost"})
535
+ print("Configuration does not exist in production")
536
+ config = client.create("database", {"host": "localhost"}, "production")
423
537
  ```
424
538
 
425
539
  #### Get with Default
426
540
 
427
541
  ```python
428
- # Get configuration or return default
542
+ # Get configuration or return default (environment REQUIRED)
429
543
  config = client.get_or_default(
430
544
  "optional_feature",
545
+ "production",
431
546
  default={"enabled": False, "timeout": 30}
432
547
  )
433
548
 
@@ -443,23 +558,33 @@ if config['value']['enabled']:
443
558
  total = client.count()
444
559
  print(f"Total configurations: {total}")
445
560
 
561
+ # Count by environment
562
+ prod_count = client.count(environment="production")
563
+ staging_count = client.count(environment="staging")
564
+ print(f"Production: {prod_count}, Staging: {staging_count}")
565
+
446
566
  # Count by category
447
- prod_count = client.count(category="production")
448
- dev_count = client.count(category="development")
449
- print(f"Production: {prod_count}, Development: {dev_count}")
567
+ db_count = client.count(category="database")
568
+
569
+ # Count by both
570
+ prod_db_count = client.count(category="database", environment="production")
450
571
  ```
451
572
 
452
- #### List Categories
573
+ #### List Categories and Environments
453
574
 
454
575
  ```python
455
576
  # Get all unique categories
456
577
  categories = client.list_categories()
457
578
  print(f"Available categories: {', '.join(categories)}")
458
579
 
459
- # Process by category
460
- for category in categories:
461
- count = client.count(category=category)
462
- print(f"{category}: {count} configurations")
580
+ # Get all unique environments
581
+ environments = client.list_environments()
582
+ print(f"Available environments: {', '.join(environments)}")
583
+
584
+ # Process by environment
585
+ for env in environments:
586
+ count = client.count(environment=env)
587
+ print(f"{env}: {count} configurations")
463
588
  ```
464
589
 
465
590
  ## 🔧 Advanced Usage
@@ -497,8 +622,8 @@ client = OpenSecureConfClient(
497
622
  )
498
623
 
499
624
  # All operations will be logged
500
- config = client.create("test", {"data": "value"})
501
- # Output: 2026-01-14 17:00:00 - opensecureconf_client - INFO - POST /configs - Status: 201 - Duration: 0.045s
625
+ config = client.create("test", {"data": "value"}, "production")
626
+ # Output: 2026-02-05 12:00:00 - opensecureconf_client - INFO - POST /configs - Status: 201 - Duration: 0.045s
502
627
  ```
503
628
 
504
629
  ### SSL Configuration
@@ -541,6 +666,53 @@ client = OpenSecureConfClient(
541
666
  )
542
667
  ```
543
668
 
669
+ ### Working with Multiple Environments
670
+
671
+ ```python
672
+ from opensecureconf_client_enhanced import OpenSecureConfClient
673
+
674
+ with OpenSecureConfClient(
675
+ base_url="http://localhost:9000",
676
+ user_key="my-key"
677
+ ) as client:
678
+ # Define configurations for different environments
679
+ environments = {
680
+ "production": {
681
+ "database": {"host": "db.prod.com", "port": 5432, "ssl": True},
682
+ "api_url": "https://api.prod.com",
683
+ "debug": False
684
+ },
685
+ "staging": {
686
+ "database": {"host": "db.staging.com", "port": 5432, "ssl": True},
687
+ "api_url": "https://api.staging.com",
688
+ "debug": True
689
+ },
690
+ "development": {
691
+ "database": {"host": "localhost", "port": 5432, "ssl": False},
692
+ "api_url": "http://localhost:3000",
693
+ "debug": True
694
+ }
695
+ }
696
+
697
+ # Create configurations for all environments
698
+ for env_name, configs in environments.items():
699
+ for key, value in configs.items():
700
+ client.create(key, value, env_name, "config")
701
+ print(f"Created {key} for {env_name}")
702
+
703
+ # Read environment-specific configuration
704
+ prod_db = client.read("database", "production")
705
+ print(f"Production database: {prod_db['value']['host']}")
706
+
707
+ # Update only staging environment
708
+ client.update("api_url", "staging", "https://api-v2.staging.com")
709
+
710
+ # Delete development configurations
711
+ dev_configs = client.list_all(environment="development")
712
+ for config in dev_configs:
713
+ client.delete(config['key'], "development")
714
+ ```
715
+
544
716
  ### Working with Clusters
545
717
 
546
718
  ```python
@@ -559,11 +731,11 @@ print(f"Connected to: {status['node_id']}")
559
731
  print(f"Cluster mode: {status['mode']}")
560
732
 
561
733
  # In REPLICA mode: writes are automatically replicated
562
- config = client.create("shared_config", {"data": "value"})
734
+ config = client.create("shared_config", {"data": "value"}, "production")
563
735
  # This configuration is now available on all nodes
564
736
 
565
737
  # In FEDERATED mode: reads check all nodes
566
- config = client.read("distributed_config")
738
+ config = client.read("distributed_config", "production")
567
739
  # This searches across all federated nodes
568
740
 
569
741
  # Monitor cluster health
@@ -613,166 +785,113 @@ OpenSecureConfClient(
613
785
 
614
786
  #### Methods
615
787
 
616
- ##### Health & Status
617
-
618
- ###### `ping() -> bool`
619
-
620
- Check if the API server is reachable.
621
-
622
- **Returns:** `True` if server is healthy, `False` otherwise
623
-
624
- **Example:**
625
- ```python
626
- if client.ping():
627
- print("Server is online")
628
- ```
629
-
630
- ###### `get_service_info() -> Dict[str, Any]`
631
-
632
- Get information about the OpenSecureConf service.
633
-
634
- **Returns:** Dictionary with service metadata
635
- - `service`: Service name
636
- - `version`: API version
637
- - `features`: List of enabled features
638
- - `cluster_enabled`: Whether clustering is enabled
639
- - `cluster_mode`: Cluster mode (if enabled)
640
-
641
- **Example:**
642
- ```python
643
- info = client.get_service_info()
644
- print(f"Version: {info['version']}")
645
- ```
646
-
647
- ##### Cluster Operations
648
-
649
- ###### `get_cluster_status() -> Dict[str, Any]`
650
-
651
- Get cluster status and node information.
652
-
653
- **Returns:** Dictionary with cluster status
654
- - `enabled`: Whether clustering is enabled
655
- - `mode`: Cluster mode (replica or federated)
656
- - `node_id`: Current node identifier
657
- - `total_nodes`: Total number of nodes
658
- - `healthy_nodes`: Number of healthy nodes
659
-
660
- **Raises:** `ClusterError` if cluster status cannot be retrieved
661
-
662
- **Example:**
663
- ```python
664
- status = client.get_cluster_status()
665
- print(f"Healthy: {status['healthy_nodes']}/{status['total_nodes']}")
666
- ```
667
-
668
- ###### `get_cluster_health() -> Dict[str, Any]`
669
-
670
- Check cluster node health.
671
-
672
- **Returns:** Dictionary with health status
673
-
674
- **Example:**
675
- ```python
676
- health = client.get_cluster_health()
677
- print(f"Status: {health['status']}")
678
- ```
679
-
680
788
  ##### Configuration CRUD
681
789
 
682
- ###### `create(key: str, value: Dict[str, Any], category: Optional[str] = None) -> Dict[str, Any]`
790
+ ###### `create(key: str, value: Union[Dict, str, int, bool, list], environment: str, category: Optional[str] = None) -> Dict[str, Any]`
683
791
 
684
792
  Create a new encrypted configuration entry.
685
793
 
686
794
  **Parameters:**
687
- - `key`: Unique configuration key (1-255 characters)
688
- - `value`: Configuration data as dictionary
795
+ - `key`: Configuration key (1-255 characters)
796
+ - `value`: Configuration data (dict, string, int, bool, or list)
797
+ - `environment`: Environment identifier (REQUIRED, max 100 characters)
689
798
  - `category`: Optional category for grouping (max 100 characters)
690
799
 
691
800
  **Returns:** Dictionary with created configuration
692
801
 
693
802
  **Raises:**
694
- - `ConfigurationExistsError`: If key already exists
803
+ - `ConfigurationExistsError`: If (key, environment) already exists
695
804
  - `ValueError`: If parameters are invalid
696
805
 
697
806
  **Example:**
698
807
  ```python
699
- config = client.create(
700
- "database",
701
- {"host": "localhost", "port": 5432},
702
- category="production"
703
- )
808
+ # Same key in different environments
809
+ prod_config = client.create("database", {"host": "db.prod.com"}, "production", "config")
810
+ staging_config = client.create("database", {"host": "db.staging.com"}, "staging", "config")
704
811
  ```
705
812
 
706
- ###### `read(key: str) -> Dict[str, Any]`
813
+ ###### `read(key: str, environment: str) -> Dict[str, Any]`
707
814
 
708
- Read and decrypt a configuration entry.
815
+ Read and decrypt a configuration entry by key and environment.
709
816
 
710
817
  **Parameters:**
711
818
  - `key`: Configuration key to retrieve
819
+ - `environment`: Environment identifier (REQUIRED)
712
820
 
713
821
  **Returns:** Dictionary with configuration
714
822
 
715
823
  **Raises:**
716
- - `ConfigurationNotFoundError`: If key does not exist
717
- - `ValueError`: If key is invalid
824
+ - `ConfigurationNotFoundError`: If (key, environment) does not exist
825
+ - `ValueError`: If parameters are invalid
718
826
 
719
827
  **Example:**
720
828
  ```python
721
- config = client.read("database")
722
- print(config['value'])
829
+ prod_config = client.read("database", "production")
830
+ staging_config = client.read("database", "staging")
723
831
  ```
724
832
 
725
- ###### `update(key: str, value: Dict[str, Any], category: Optional[str] = None) -> Dict[str, Any]`
833
+ ###### `update(key: str, environment: str, value: Union[Dict, str, int, bool, list], category: Optional[str] = None) -> Dict[str, Any]`
726
834
 
727
835
  Update an existing configuration entry.
728
836
 
729
837
  **Parameters:**
730
838
  - `key`: Configuration key to update
839
+ - `environment`: Environment identifier (REQUIRED, cannot be changed)
731
840
  - `value`: New configuration data
732
841
  - `category`: Optional new category
733
842
 
734
843
  **Returns:** Dictionary with updated configuration
735
844
 
736
845
  **Raises:**
737
- - `ConfigurationNotFoundError`: If key does not exist
846
+ - `ConfigurationNotFoundError`: If (key, environment) does not exist
738
847
  - `ValueError`: If parameters are invalid
739
848
 
740
849
  **Example:**
741
850
  ```python
742
- updated = client.update("database", {"host": "db.example.com", "port": 5432})
851
+ updated = client.update("database", "production", {"host": "db-new.prod.com"})
743
852
  ```
744
853
 
745
- ###### `delete(key: str) -> Dict[str, str]`
854
+ ###### `delete(key: str, environment: str) -> Dict[str, str]`
746
855
 
747
- Delete a configuration entry permanently.
856
+ Delete a configuration entry permanently from specific environment.
748
857
 
749
858
  **Parameters:**
750
859
  - `key`: Configuration key to delete
860
+ - `environment`: Environment identifier (REQUIRED)
751
861
 
752
862
  **Returns:** Dictionary with success message
753
863
 
754
864
  **Raises:**
755
- - `ConfigurationNotFoundError`: If key does not exist
756
- - `ValueError`: If key is invalid
865
+ - `ConfigurationNotFoundError`: If (key, environment) does not exist
866
+ - `ValueError`: If parameters are invalid
757
867
 
758
868
  **Example:**
759
869
  ```python
760
- result = client.delete("database")
761
- print(result['message'])
870
+ # Delete from staging only
871
+ result = client.delete("database", "staging")
872
+ # Production remains untouched
762
873
  ```
763
874
 
764
- ###### `list_all(category: Optional[str] = None) -> List[Dict[str, Any]]`
875
+ ###### `list_all(category: Optional[str] = None, environment: Optional[str] = None) -> List[Dict[str, Any]]`
765
876
 
766
- List all configurations with optional category filter.
877
+ List all configurations with optional filters.
767
878
 
768
879
  **Parameters:**
769
880
  - `category`: Optional category filter
881
+ - `environment`: Optional environment filter
770
882
 
771
883
  **Returns:** List of configuration dictionaries
772
884
 
773
885
  **Example:**
774
886
  ```python
775
- configs = client.list_all(category="production")
887
+ # List all production configurations
888
+ prod_configs = client.list_all(environment="production")
889
+
890
+ # List all database configurations
891
+ db_configs = client.list_all(category="database")
892
+
893
+ # List production database configurations
894
+ configs = client.list_all(category="database", environment="production")
776
895
  ```
777
896
 
778
897
  ##### Batch Operations
@@ -782,100 +901,110 @@ configs = client.list_all(category="production")
782
901
  Create multiple configurations in batch.
783
902
 
784
903
  **Parameters:**
785
- - `configs`: List of configuration dictionaries
904
+ - `configs`: List of configuration dictionaries with 'key', 'value', 'environment' (REQUIRED), and optional 'category'
786
905
  - `ignore_errors`: Continue on errors and return partial results
787
906
 
788
907
  **Returns:** List of created configurations
789
908
 
790
909
  **Raises:**
791
- - `ValueError`: If configs format is invalid
910
+ - `ValueError`: If configs format is invalid or environment is missing
792
911
  - `OpenSecureConfError`: If creation fails and ignore_errors is False
793
912
 
794
913
  **Example:**
795
914
  ```python
796
915
  configs = [
797
- {"key": "db1", "value": {"host": "localhost"}, "category": "prod"},
798
- {"key": "db2", "value": {"host": "remote"}, "category": "prod"}
916
+ {"key": "db", "value": {"host": "localhost"}, "environment": "production", "category": "config"},
917
+ {"key": "db", "value": {"host": "localhost"}, "environment": "staging", "category": "config"}
799
918
  ]
800
919
  results = client.bulk_create(configs)
801
920
  ```
802
921
 
803
- ###### `bulk_read(keys: List[str], ignore_errors: bool = False) -> List[Dict[str, Any]]`
922
+ ###### `bulk_read(items: List[Dict[str, str]], ignore_errors: bool = False) -> List[Dict[str, Any]]`
804
923
 
805
924
  Read multiple configurations in batch.
806
925
 
807
926
  **Parameters:**
808
- - `keys`: List of configuration keys
927
+ - `items`: List of dictionaries with 'key' and 'environment' fields
809
928
  - `ignore_errors`: Skip missing keys and return partial results
810
929
 
811
930
  **Returns:** List of configuration dictionaries
812
931
 
813
932
  **Example:**
814
933
  ```python
815
- configs = client.bulk_read(["db1", "db2", "api"])
934
+ items = [
935
+ {"key": "database", "environment": "production"},
936
+ {"key": "database", "environment": "staging"}
937
+ ]
938
+ configs = client.bulk_read(items)
816
939
  ```
817
940
 
818
- ###### `bulk_delete(keys: List[str], ignore_errors: bool = False) -> Dict[str, Any]`
941
+ ###### `bulk_delete(items: List[Dict[str, str]], ignore_errors: bool = False) -> Dict[str, Any]`
819
942
 
820
943
  Delete multiple configurations in batch.
821
944
 
822
945
  **Parameters:**
823
- - `keys`: List of configuration keys
946
+ - `items`: List of dictionaries with 'key' and 'environment' fields
824
947
  - `ignore_errors`: Continue on errors
825
948
 
826
949
  **Returns:** Dictionary with summary: `{"deleted": [...], "failed": [...]}`
827
950
 
828
951
  **Example:**
829
952
  ```python
830
- result = client.bulk_delete(["temp1", "temp2", "temp3"])
831
- print(f"Deleted: {len(result['deleted'])}")
953
+ items = [
954
+ {"key": "temp1", "environment": "staging"},
955
+ {"key": "temp2", "environment": "staging"}
956
+ ]
957
+ result = client.bulk_delete(items)
832
958
  ```
833
959
 
834
960
  ##### Utility Methods
835
961
 
836
- ###### `exists(key: str) -> bool`
962
+ ###### `exists(key: str, environment: str) -> bool`
837
963
 
838
- Check if a configuration key exists.
964
+ Check if a configuration key exists in specific environment.
839
965
 
840
966
  **Parameters:**
841
967
  - `key`: Configuration key to check
968
+ - `environment`: Environment identifier (REQUIRED)
842
969
 
843
- **Returns:** `True` if key exists, `False` otherwise
970
+ **Returns:** `True` if key exists in the environment, `False` otherwise
844
971
 
845
972
  **Example:**
846
973
  ```python
847
- if client.exists("database"):
848
- config = client.read("database")
974
+ if client.exists("database", "production"):
975
+ config = client.read("database", "production")
849
976
  ```
850
977
 
851
- ###### `get_or_default(key: str, default: Dict[str, Any]) -> Dict[str, Any]`
978
+ ###### `get_or_default(key: str, environment: str, default: Union[Dict, str, int, bool, list]) -> Dict[str, Any]`
852
979
 
853
980
  Get configuration or return default if not found.
854
981
 
855
982
  **Parameters:**
856
983
  - `key`: Configuration key to retrieve
984
+ - `environment`: Environment identifier (REQUIRED)
857
985
  - `default`: Default value to return if not found
858
986
 
859
987
  **Returns:** Configuration dictionary or default
860
988
 
861
989
  **Example:**
862
990
  ```python
863
- config = client.get_or_default("optional", {"enabled": False})
991
+ config = client.get_or_default("optional", "production", {"enabled": False})
864
992
  ```
865
993
 
866
- ###### `count(category: Optional[str] = None) -> int`
994
+ ###### `count(category: Optional[str] = None, environment: Optional[str] = None) -> int`
867
995
 
868
- Count configurations, optionally filtered by category.
996
+ Count configurations, optionally filtered by category and/or environment.
869
997
 
870
998
  **Parameters:**
871
999
  - `category`: Optional category filter
1000
+ - `environment`: Optional environment filter
872
1001
 
873
1002
  **Returns:** Number of configurations
874
1003
 
875
1004
  **Example:**
876
1005
  ```python
877
1006
  total = client.count()
878
- prod_count = client.count(category="production")
1007
+ prod_count = client.count(environment="production")
879
1008
  ```
880
1009
 
881
1010
  ###### `list_categories() -> List[str]`
@@ -887,7 +1016,18 @@ Get list of all unique categories.
887
1016
  **Example:**
888
1017
  ```python
889
1018
  categories = client.list_categories()
890
- print(f"Categories: {', '.join(categories)}")
1019
+ ```
1020
+
1021
+ ###### `list_environments() -> List[str]`
1022
+
1023
+ Get list of all unique environments.
1024
+
1025
+ **Returns:** Sorted list of environment names
1026
+
1027
+ **Example:**
1028
+ ```python
1029
+ environments = client.list_environments()
1030
+ print(f"Environments: {', '.join(environments)}")
891
1031
  ```
892
1032
 
893
1033
  ##### Session Management
@@ -908,7 +1048,7 @@ The client supports context manager protocol for automatic cleanup.
908
1048
  **Example:**
909
1049
  ```python
910
1050
  with OpenSecureConfClient(base_url="...", user_key="...") as client:
911
- config = client.create("key", {"value": "data"})
1051
+ config = client.create("key", {"value": "data"}, "production")
912
1052
  # Session automatically closed
913
1053
  ```
914
1054
 
@@ -919,8 +1059,8 @@ with OpenSecureConfClient(base_url="...", user_key="...") as client:
919
1059
  ```
920
1060
  OpenSecureConfError (base exception)
921
1061
  ├── AuthenticationError # Invalid or missing credentials
922
- ├── ConfigurationNotFoundError # Configuration key does not exist
923
- ├── ConfigurationExistsError # Configuration key already exists
1062
+ ├── ConfigurationNotFoundError # Configuration (key, environment) does not exist
1063
+ ├── ConfigurationExistsError # Configuration (key, environment) already exists
924
1064
  └── ClusterError # Cluster operation failed
925
1065
  ```
926
1066
 
@@ -937,14 +1077,14 @@ from opensecureconf_client import (
937
1077
  )
938
1078
 
939
1079
  try:
940
- config = client.create("mykey", {"data": "value"})
1080
+ config = client.create("mykey", {"data": "value"}, "production")
941
1081
  except AuthenticationError:
942
1082
  print("Authentication failed - check your user_key and api_key")
943
1083
  except ConfigurationExistsError:
944
- print("Configuration already exists - use update() instead")
945
- config = client.update("mykey", {"data": "new_value"})
1084
+ print("Configuration already exists in this environment - use update() instead")
1085
+ config = client.update("mykey", "production", {"data": "new_value"})
946
1086
  except ConfigurationNotFoundError:
947
- print("Configuration not found")
1087
+ print("Configuration not found in specified environment")
948
1088
  except ClusterError as e:
949
1089
  print(f"Cluster error: {e}")
950
1090
  except OpenSecureConfError as e:
@@ -958,20 +1098,20 @@ except ConnectionError as e:
958
1098
  ```python
959
1099
  # 1. Use specific exceptions first
960
1100
  try:
961
- config = client.read("important_config")
1101
+ config = client.read("important_config", "production")
962
1102
  except ConfigurationNotFoundError:
963
1103
  # Create with defaults if not found
964
- config = client.create("important_config", {"default": "value"})
1104
+ config = client.create("important_config", {"default": "value"}, "production")
965
1105
 
966
1106
  # 2. Use exists() to avoid exceptions
967
- if not client.exists("optional_config"):
968
- client.create("optional_config", {"data": "value"})
1107
+ if not client.exists("optional_config", "production"):
1108
+ client.create("optional_config", {"data": "value"}, "production")
969
1109
 
970
1110
  # 3. Use get_or_default() for optional configurations
971
- config = client.get_or_default("optional", {"enabled": False})
1111
+ config = client.get_or_default("optional", "production", {"enabled": False})
972
1112
 
973
1113
  # 4. Handle bulk operation failures
974
- result = client.bulk_delete(keys, ignore_errors=True)
1114
+ result = client.bulk_delete(items, ignore_errors=True)
975
1115
  if result['failed']:
976
1116
  print(f"Some deletions failed: {result['failed']}")
977
1117
  ```
@@ -1045,45 +1185,71 @@ export OSC_LOG_LEVEL="INFO"
1045
1185
  ```python
1046
1186
  # ✅ Good: Automatic cleanup
1047
1187
  with OpenSecureConfClient(base_url="...", user_key="...") as client:
1048
- config = client.create("key", {"data": "value"})
1188
+ config = client.create("key", {"data": "value"}, "production")
1049
1189
 
1050
1190
  # ❌ Avoid: Manual cleanup required
1051
1191
  client = OpenSecureConfClient(base_url="...", user_key="...")
1052
- config = client.create("key", {"data": "value"})
1192
+ config = client.create("key", {"data": "value"}, "production")
1053
1193
  client.close() # Easy to forget
1054
1194
  ```
1055
1195
 
1056
- ### 2. Check Existence Before Operations
1196
+ ### 2. Always Specify Environment
1197
+
1198
+ ```python
1199
+ # ✅ Good: Explicit environment
1200
+ config = client.create("api_url", "https://api.com", "production")
1201
+ prod_config = client.read("api_url", "production")
1202
+
1203
+ # ❌ Error: Environment is now required
1204
+ # config = client.create("api_url", "https://api.com") # This will fail
1205
+ ```
1206
+
1207
+ ### 3. Use Multi-Environment Configuration
1208
+
1209
+ ```python
1210
+ # ✅ Good: Separate configurations per environment
1211
+ for env in ["production", "staging", "development"]:
1212
+ client.create("database", get_db_config(env), env, "config")
1213
+
1214
+ # ❌ Avoid: Mixing environments in one key
1215
+ client.create("database_prod", prod_config, "production")
1216
+ client.create("database_staging", staging_config, "production")
1217
+ ```
1218
+
1219
+ ### 4. Check Existence Before Operations
1057
1220
 
1058
1221
  ```python
1059
1222
  # ✅ Good: Avoid exceptions
1060
- if not client.exists("config"):
1061
- client.create("config", {"data": "value"})
1223
+ if not client.exists("config", "production"):
1224
+ client.create("config", {"data": "value"}, "production")
1062
1225
 
1063
1226
  # ❌ Avoid: Exception handling for flow control
1064
1227
  try:
1065
- client.create("config", {"data": "value"})
1228
+ client.create("config", {"data": "value"}, "production")
1066
1229
  except ConfigurationExistsError:
1067
1230
  pass
1068
1231
  ```
1069
1232
 
1070
- ### 3. Use Batch Operations for Multiple Items
1233
+ ### 5. Use Batch Operations for Multiple Items
1071
1234
 
1072
1235
  ```python
1073
1236
  # ✅ Good: Single batch operation
1074
- keys = ["config1", "config2", "config3"]
1075
- configs = client.bulk_read(keys, ignore_errors=True)
1237
+ items = [
1238
+ {"key": "config1", "environment": "production"},
1239
+ {"key": "config2", "environment": "production"}
1240
+ ]
1241
+ configs = client.bulk_read(items, ignore_errors=True)
1076
1242
 
1077
1243
  # ❌ Avoid: Multiple individual requests
1078
1244
  configs = []
1079
- for key in keys:
1245
+ for item in items:
1080
1246
  try:
1081
- configs.append(client.read(key))
1247
+ configs.append(client.read(item["key"], item["environment"]))
1082
1248
  except:
1083
1249
  pass
1084
1250
  ```
1085
1251
 
1086
- ### 4. Enable Retry for Production
1252
+ ### 6. Enable Retry for Production
1087
1253
 
1088
1254
  ```python
1089
1255
  # ✅ Good: Resilient to transient failures
@@ -1102,7 +1268,7 @@ client = OpenSecureConfClient(
1102
1268
  )
1103
1269
  ```
1104
1270
 
1105
- ### 5. Use Structured Logging
1271
+ ### 7. Use Structured Logging
1106
1272
 
1107
1273
  ```python
1108
1274
  # ✅ Good: Enable logging for production monitoring
@@ -1120,7 +1286,7 @@ client = OpenSecureConfClient(
1120
1286
  )
1121
1287
  ```
1122
1288
 
1123
- ### 6. Secure Credential Management
1289
+ ### 8. Secure Credential Management
1124
1290
 
1125
1291
  ```python
1126
1292
  # ✅ Good: Load from environment or secrets manager
@@ -1141,7 +1307,7 @@ client = OpenSecureConfClient(
1141
1307
  )
1142
1308
  ```
1143
1309
 
1144
- ### 7. Monitor Cluster Health
1310
+ ### 9. Monitor Cluster Health
1145
1311
 
1146
1312
  ```python
1147
1313
  # ✅ Good: Regular health checks
@@ -1152,7 +1318,20 @@ if client.ping():
1152
1318
  logger.warning(f"Cluster degraded: {status['healthy_nodes']}/{status['total_nodes']}")
1153
1319
 
1154
1320
  # ❌ Avoid: No health monitoring
1155
- config = client.read("config") # Might fail silently in degraded cluster
1321
+ config = client.read("config", "production") # Might fail silently in degraded cluster
1322
+ ```
1323
+
1324
+ ### 10. Organize by Environment
1325
+
1326
+ ```python
1327
+ # ✅ Good: Clear environment separation
1328
+ environments = client.list_environments()
1329
+ for env in environments:
1330
+ configs = client.list_all(environment=env)
1331
+ print(f"{env}: {len(configs)} configurations")
1332
+
1333
+ # ❌ Avoid: Mixing all environments
1334
+ all_configs = client.list_all() # Hard to manage
1156
1335
  ```
1157
1336
 
1158
1337
  ## 🔨 Development
@@ -1284,6 +1463,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
1284
1463
  - Encryption powered by [cryptography](https://cryptography.io/)
1285
1464
  - HTTP client using [requests](https://requests.readthedocs.io/)
1286
1465
 
1287
- ---
1466
+ ***
1288
1467
 
1289
1468
  **Made with ❤️ by the OpenSecureConf Team**