ibm-appconfiguration-python-sdk 0.3.7__tar.gz → 0.3.9__tar.gz

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 (58) hide show
  1. {ibm_appconfiguration_python_sdk-0.3.7/ibm_appconfiguration_python_sdk.egg-info → ibm_appconfiguration_python_sdk-0.3.9}/PKG-INFO +15 -11
  2. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/README.md +1 -9
  3. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/appconfiguration.py +9 -0
  4. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/configuration_handler.py +34 -10
  5. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/file_manager.py +7 -8
  6. ibm_appconfiguration_python_sdk-0.3.9/ibm_appconfiguration/configurations/internal/utils/parser.py +149 -0
  7. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/url_builder.py +2 -2
  8. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/rule.py +31 -7
  9. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/version.py +1 -1
  10. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9/ibm_appconfiguration_python_sdk.egg-info}/PKG-INFO +15 -11
  11. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/SOURCES.txt +1 -0
  12. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/setup.py +1 -1
  13. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_rule.py +55 -0
  14. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/test_configuration_handler.py +87 -68
  15. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_api_manager.py +14 -3
  16. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_file_manager.py +2 -2
  17. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_url_builder.py +4 -4
  18. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/test_appconfiguration.py +1 -1
  19. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/LICENSE +0 -0
  20. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/__init__.py +0 -0
  21. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/sample_app.py +0 -0
  22. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/server_sample.py +0 -0
  23. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/__init__.py +0 -0
  24. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/__init__.py +0 -0
  25. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/__init__.py +0 -0
  26. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/__init__.py +0 -0
  27. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/config_constants.py +0 -0
  28. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/config_messages.py +0 -0
  29. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/__init__.py +0 -0
  30. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/api_manager.py +0 -0
  31. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/compute_percentage.py +0 -0
  32. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/connectivity.py +0 -0
  33. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/logger.py +0 -0
  34. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/metering.py +0 -0
  35. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/socket.py +0 -0
  36. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/validators.py +0 -0
  37. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/__init__.py +0 -0
  38. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/configuration_type.py +0 -0
  39. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/feature.py +0 -0
  40. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/property.py +0 -0
  41. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/segment.py +0 -0
  42. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/segment_rules.py +0 -0
  43. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/dependency_links.txt +0 -0
  44. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/requires.txt +0 -0
  45. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/top_level.txt +0 -0
  46. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/integration_tests/__init__.py +0 -0
  47. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/integration_tests/test_integration.py +0 -0
  48. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/setup.cfg +0 -0
  49. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/__init__.py +0 -0
  50. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/__init__.py +0 -0
  51. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/__init__.py +0 -0
  52. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_feature.py +0 -0
  53. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_property.py +0 -0
  54. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_segment.py +0 -0
  55. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_segment_rules.py +0 -0
  56. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/__init__.py +0 -0
  57. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_metering.py +0 -0
  58. {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_socket.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ibm-appconfiguration-python-sdk
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary: IBM Cloud App Configuration Python SDK
5
5
  Home-page: https://github.com/IBM/appconfiguration-python-sdk
6
6
  Author: IBM
@@ -21,6 +21,18 @@ Requires-Dist: ibm-cloud-sdk-core<4.0.0,>=3.20.3
21
21
  Requires-Dist: pyyaml>=5.4.1
22
22
  Requires-Dist: schema>=0.7.5
23
23
  Requires-Dist: mmh3==5.0.1
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: keywords
31
+ Dynamic: license
32
+ Dynamic: license-file
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
24
36
 
25
37
  # IBM Cloud App Configuration Python server SDK
26
38
 
@@ -74,15 +86,7 @@ The **`init()`** and **`set_context()`** are the initialisation methods and shou
74
86
  appconfig_client. The appconfig_client, once initialised, can be obtained across modules
75
87
  using **`AppConfiguration.get_instance()`**. [See this example below](#fetching-the-appconfig_client-across-other-modules).
76
88
 
77
- - region : Region name where the App Configuration service instance is created. Use
78
- - `AppConfiguration.REGION_US_SOUTH` for Dallas
79
- - `AppConfiguration.REGION_EU_GB` for London
80
- - `AppConfiguration.REGION_AU_SYD` for Sydney
81
- - `AppConfiguration.REGION_US_EAST` for Washington DC
82
- - `AppConfiguration.REGION_EU_DE` for Frankfurt
83
- - `AppConfiguration.REGION_CA_TOR` for Toronto
84
- - `AppConfiguration.REGION_JP_TOK` for Tokyo
85
- - `AppConfiguration.REGION_JP_OSA` for Osaka
89
+ - region : Region name where the App Configuration service instance is created. See list of supported locations [here](https://cloud.ibm.com/catalog/services/app-configuration). Eg:- `us-south`, `au-syd` etc.
86
90
  - guid : GUID of the App Configuration service. Obtain it from the service credentials section of the dashboard
87
91
  - apikey : ApiKey of the App Configuration service. Obtain it from the service credentials section of the dashboard
88
92
  - collection_id : Id of the collection created in App Configuration service instance under the **Collections** section.
@@ -50,15 +50,7 @@ The **`init()`** and **`set_context()`** are the initialisation methods and shou
50
50
  appconfig_client. The appconfig_client, once initialised, can be obtained across modules
51
51
  using **`AppConfiguration.get_instance()`**. [See this example below](#fetching-the-appconfig_client-across-other-modules).
52
52
 
53
- - region : Region name where the App Configuration service instance is created. Use
54
- - `AppConfiguration.REGION_US_SOUTH` for Dallas
55
- - `AppConfiguration.REGION_EU_GB` for London
56
- - `AppConfiguration.REGION_AU_SYD` for Sydney
57
- - `AppConfiguration.REGION_US_EAST` for Washington DC
58
- - `AppConfiguration.REGION_EU_DE` for Frankfurt
59
- - `AppConfiguration.REGION_CA_TOR` for Toronto
60
- - `AppConfiguration.REGION_JP_TOK` for Tokyo
61
- - `AppConfiguration.REGION_JP_OSA` for Osaka
53
+ - region : Region name where the App Configuration service instance is created. See list of supported locations [here](https://cloud.ibm.com/catalog/services/app-configuration). Eg:- `us-south`, `au-syd` etc.
62
54
  - guid : GUID of the App Configuration service. Obtain it from the service credentials section of the dashboard
63
55
  - apikey : ApiKey of the App Configuration service. Obtain it from the service credentials section of the dashboard
64
56
  - collection_id : Id of the collection created in App Configuration service instance under the **Collections** section.
@@ -298,3 +298,12 @@ class AppConfiguration:
298
298
  return self.__configuration_handler_instance.get_property(property_id)
299
299
  Logger.error(config_messages.COLLECTION_INIT_ERROR)
300
300
  return None
301
+
302
+ def is_connected(self) -> bool:
303
+ """ Get the status of server-client connection
304
+
305
+ Returns: boolean indicating connection status
306
+ """
307
+ if self.__configuration_handler_instance is None:
308
+ return False
309
+ return self.__configuration_handler_instance.is_connected()
@@ -15,11 +15,14 @@
15
15
  """
16
16
  Internal class to handle the configuration.
17
17
  """
18
+ import json
18
19
  import os
19
20
  from typing import Dict, List, Any
20
21
  from threading import Timer, Thread
21
22
  from ibm_appconfiguration.configurations.internal.common import config_messages, config_constants
23
+ from ibm_appconfiguration.version import __version__
22
24
  from .internal.utils.logger import Logger
25
+ from .internal.utils.parser import extract_configurations, format_config
23
26
  from .internal.utils.validators import Validators
24
27
  from .models import Feature
25
28
  from .models import SegmentRules
@@ -46,6 +49,9 @@ class ConfigurationHandler:
46
49
  """Internal class to handle the configuration"""
47
50
  __instance = None
48
51
 
52
+ # variable to keep track of server-client connection status
53
+ __is_alive = False
54
+
49
55
  @staticmethod
50
56
  def get_instance():
51
57
  """ Static access method. """
@@ -144,7 +150,9 @@ class ConfigurationHandler:
144
150
  self.__persistent_data = FileManager.read_files(
145
151
  file_path=os.path.join(self.__persistent_cache_dir, 'appconfiguration.json'))
146
152
  if self.__persistent_data is not None:
147
- self.__load_configurations(self.__persistent_data)
153
+ self.__load_configurations(
154
+ extract_configurations(self.__persistent_data, self.__environment_id, self.__collection_id)
155
+ )
148
156
  if not os.access(self.__persistent_cache_dir, os.W_OK):
149
157
  Logger.error(config_messages.ERROR_NO_WRITE_PERMISSION)
150
158
  return
@@ -153,11 +161,13 @@ class ConfigurationHandler:
153
161
  if self.__persistent_data is None or len(self.__persistent_data) == 0:
154
162
  bootstrap_file_data = FileManager.read_files(file_path=self.__bootstrap_file)
155
163
  if bootstrap_file_data is not None:
156
- self.__load_configurations(bootstrap_file_data)
164
+ configurations = extract_configurations(bootstrap_file_data, self.__environment_id, self.__collection_id)
165
+ self.__load_configurations(configurations)
166
+ self.__write_to_persistent_storage(format_config(configurations, self.__environment_id, self.__collection_id),
167
+ self.__persistent_cache_dir)
157
168
  else:
158
169
  Logger.error("Error reading bootstrap file data")
159
170
  return
160
- self.__write_to_persistent_storage(bootstrap_file_data, self.__persistent_cache_dir)
161
171
  if self.__configuration_update_listener and callable(self.__configuration_update_listener):
162
172
  self.__configuration_update_listener()
163
173
  else:
@@ -166,7 +176,9 @@ class ConfigurationHandler:
166
176
  else:
167
177
  bootstrap_file_data = FileManager.read_files(file_path=self.__bootstrap_file)
168
178
  if bootstrap_file_data is not None:
169
- self.__load_configurations(bootstrap_file_data)
179
+ self.__load_configurations(
180
+ extract_configurations(bootstrap_file_data, self.__environment_id, self.__collection_id)
181
+ )
170
182
  else:
171
183
  Logger.error("Error reading bootstrap file data")
172
184
  return
@@ -268,7 +280,8 @@ class ConfigurationHandler:
268
280
  def __start_web_socket(self):
269
281
  bearer_token = URLBuilder.get_iam_authenticator().token_manager.get_token()
270
282
  headers = {
271
- 'Authorization': 'Bearer ' + bearer_token
283
+ 'Authorization': 'Bearer ' + bearer_token,
284
+ 'User-Agent': '{0}/{1}'.format(config_constants.SDK_NAME, __version__)
272
285
  }
273
286
  if self.__socket:
274
287
  self.__socket.cancel()
@@ -475,8 +488,8 @@ class ConfigurationHandler:
475
488
  Logger.debug(err)
476
489
  return rule_map
477
490
 
478
- def __write_to_persistent_storage(self, json: dict, file_path: str):
479
- FileManager.store_files(json, os.path.join(file_path, 'appconfiguration.json'))
491
+ def __write_to_persistent_storage(self, data: str, file_path: str):
492
+ FileManager.store_files(json.dumps(json.loads(data), indent=2), os.path.join(file_path, 'appconfiguration.json'))
480
493
 
481
494
  def __fetch_from_api(self):
482
495
  if self.__is_initialized:
@@ -503,8 +516,8 @@ class ConfigurationHandler:
503
516
  Logger.info(config_messages.CONFIGURATIONS_FETCH_SUCCESS)
504
517
  response_data = response.get_result()
505
518
  try:
506
- response_data = dict(response_data)
507
- self.__load_configurations(response_data) # load response to cache maps
519
+ configurations = extract_configurations(json.dumps(response_data), self.__environment_id, self.__collection_id)
520
+ self.__load_configurations(configurations) # load response to cache maps
508
521
  if self.__configuration_update_listener and callable(self.__configuration_update_listener):
509
522
  self.__configuration_update_listener()
510
523
  # we have already loaded the configurations to feature & property dicts.
@@ -512,7 +525,7 @@ class ConfigurationHandler:
512
525
  # But the thread shouldn't be a daemon thread, because the writing should complete even if the main thread has terminated.
513
526
  if self.__persistent_cache_dir:
514
527
  file_write_thread = Thread(target=self.__write_to_persistent_storage,
515
- args=(response_data, self.__persistent_cache_dir,))
528
+ args=(format_config(configurations, self.__environment_id, self.__collection_id), self.__persistent_cache_dir))
516
529
  file_write_thread.start()
517
530
  except Exception as exception:
518
531
  Logger.error(f'error while while fetching {exception}')
@@ -538,24 +551,35 @@ class ConfigurationHandler:
538
551
  def __on_web_socket_callback(self, message=None, error_state=None,
539
552
  closed_state=None, open_state=None):
540
553
  if message:
554
+ self.__is_alive = True
541
555
  self.__fetch_from_api()
542
556
  Logger.debug(f'Received message from socket. {message}')
543
557
  elif error_state:
558
+ self.__is_alive = False
544
559
  Logger.error(f'Received error from socket. {error_state}')
545
560
  Logger.info('Reconnecting to server....')
546
561
  self.__on_socket_retry = True
547
562
  sleep(delay)
548
563
  self.__start_web_socket()
549
564
  elif closed_state:
565
+ self.__is_alive = False
550
566
  Logger.error('Received close connection from socket.')
551
567
  Logger.info('Reconnecting to server....')
552
568
  self.__on_socket_retry = True
553
569
  sleep(delay)
554
570
  self.__start_web_socket()
555
571
  elif open_state:
572
+ self.__is_alive = True
556
573
  if self.__on_socket_retry:
557
574
  self.__on_socket_retry = False
558
575
  self.__fetch_from_api()
559
576
  Logger.debug('Received opened connection from socket.')
560
577
  else:
561
578
  Logger.error('Unknown Error inside the socket connection.')
579
+
580
+ def is_connected(self) -> bool:
581
+ """ Get the status of server-client connection
582
+
583
+ Returns: boolean indicating connection status
584
+ """
585
+ return self.__is_alive
@@ -18,8 +18,7 @@ file based cache of the SDK.
18
18
  """
19
19
 
20
20
  import fcntl
21
- import json
22
- from typing import Optional, Any
21
+ from typing import Optional
23
22
 
24
23
  from .logger import Logger
25
24
 
@@ -28,17 +27,17 @@ class FileManager:
28
27
  """FileManager to handle the cache"""
29
28
 
30
29
  @classmethod
31
- def store_files(cls, json_data: {}, file_path: str) -> bool:
30
+ def store_files(cls, data: str, file_path: str) -> bool:
32
31
  """Store the file
33
32
 
34
33
  Args:
35
- json_data: Data to be stored.
34
+ data: Data to be stored.
36
35
  file_path: File path for the cache.
37
36
  """
38
37
  try:
39
38
  with open(file_path, 'w') as cache:
40
39
  fcntl.flock(cache, fcntl.LOCK_EX | fcntl.LOCK_NB)
41
- json.dump(json_data, cache)
40
+ cache.write(data)
42
41
  fcntl.flock(cache, fcntl.LOCK_UN)
43
42
  return True
44
43
  except Exception as err:
@@ -46,7 +45,7 @@ class FileManager:
46
45
  return False
47
46
 
48
47
  @classmethod
49
- def read_files(cls, file_path: str) -> Optional[Any]:
48
+ def read_files(cls, file_path: str) -> Optional[str]:
50
49
  """
51
50
  Read the data from the given path.
52
51
 
@@ -59,9 +58,9 @@ class FileManager:
59
58
  try:
60
59
  with open(file_path, 'r') as file:
61
60
  fcntl.flock(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
62
- data = json.load(file)
61
+ data = file.read()
63
62
  fcntl.flock(file, fcntl.LOCK_UN)
64
- return data
63
+ return data if len(data) > 0 else None
65
64
  except Exception as err:
66
65
  Logger.error(err)
67
66
  return None
@@ -0,0 +1,149 @@
1
+ import json
2
+ from typing import Any, Dict, List, Set
3
+
4
+
5
+ def extract_environment_data(data: Dict[str, Any], environment_id: str) -> Dict[str, Any]:
6
+ """
7
+ Prepares config data for extraction with validation.
8
+
9
+ :param data: The full JSON configuration as a dictionary.
10
+ :param environment_id: The environment ID to extract.
11
+ :return: A dictionary containing 'features', 'properties', and 'segments'.
12
+ :raises: Exception if the format is invalid or environment not found.
13
+ """
14
+ if not isinstance(data.get("segments"), list) or not isinstance(data.get("environments"), list):
15
+ raise Exception("Improper Data format present in configuration")
16
+
17
+ for environment in data["environments"]:
18
+ if environment.get("environment_id") == environment_id:
19
+ return {
20
+ "features": environment.get("features", []),
21
+ "properties": environment.get("properties", []),
22
+ "segments": data["segments"]
23
+ }
24
+
25
+ raise Exception("Matching environment not found in configuration")
26
+
27
+
28
+ def validate_resource(resource: Dict[str, Any], collection: str) -> bool:
29
+ """
30
+ Validates if the feature/property belongs to the given collection.
31
+
32
+ :param resource: The feature or property dictionary.
33
+ :param collection: The collection ID to match.
34
+ :return: True if valid, False otherwise.
35
+ :raises: Exception if collection format is invalid.
36
+ """
37
+ if "collections" not in resource:
38
+ return True
39
+
40
+ collections = resource["collections"]
41
+ if not isinstance(collections, list):
42
+ raise Exception("Improper collection format in resource data")
43
+
44
+ for col in collections:
45
+ if col.get("collection_id") == collection:
46
+ return True
47
+
48
+ return False
49
+
50
+
51
+ def append_segment_ids(resource: Dict[str, Any], segment_ids: Set[str]):
52
+ """
53
+ Appends segment IDs from the resource's segment rules into the given set.
54
+
55
+ :param resource: The feature or property dictionary.
56
+ :param segment_ids: A set to accumulate segment IDs.
57
+ """
58
+ for segment_rule in resource.get("segment_rules", []):
59
+ for rule in segment_rule.get("rules", []):
60
+ for segment_id in rule.get("segments", []):
61
+ segment_ids.add(segment_id)
62
+
63
+
64
+ def extract_resources(resource_data: Dict[str, Any], collection: str) -> Dict[str, List[Any]]:
65
+ """
66
+ Extracts features, properties, and segments after validation.
67
+
68
+ :param resource_data: The environment-specific data.
69
+ :param collection: The collection ID to validate against.
70
+ :return: A dictionary with keys 'features', 'properties', and 'segments'.
71
+ :raises: Exception if any required segment is missing.
72
+ """
73
+ features = []
74
+ properties = []
75
+ segments = []
76
+ required_segment_ids = set()
77
+
78
+ for feature in resource_data.get("features", []):
79
+ if validate_resource(feature, collection):
80
+ append_segment_ids(feature, required_segment_ids)
81
+ features.append(feature)
82
+
83
+ for property_ in resource_data.get("properties", []):
84
+ if validate_resource(property_, collection):
85
+ append_segment_ids(property_, required_segment_ids)
86
+ properties.append(property_)
87
+
88
+ available_segments = resource_data.get("segments", [])
89
+ for segment in available_segments:
90
+ if segment.get("segment_id") in required_segment_ids:
91
+ segments.append(segment)
92
+ required_segment_ids.remove(segment.get("segment_id"))
93
+
94
+ if len(required_segment_ids) > 0:
95
+ raise Exception(f"Required segment doesn't exist in provided segments")
96
+
97
+ return {
98
+ "features": features,
99
+ "properties": properties,
100
+ "segments": segments
101
+ }
102
+
103
+
104
+ def extract_configurations(data: str, environment: str, collection: str) -> Dict[str, List[Any]]:
105
+ """
106
+ Unified parser for app-config data for new SDK/export/promote format.
107
+
108
+ :param data: Raw JSON string of the config.
109
+ :param environment: The environment ID.
110
+ :param collection: The collection ID.
111
+ :return: A dictionary with 'features', 'properties', and 'segments'.
112
+ :raises: Exception on any validation or format error.
113
+ """
114
+ try:
115
+ configurations = json.loads(data)
116
+
117
+ if "collections" not in configurations or not isinstance(configurations["collections"], list):
118
+ raise Exception("Improper/Missing collections in configuration")
119
+
120
+ if not any(col.get("collection_id") == collection for col in configurations["collections"]):
121
+ raise Exception("Required collection not found in collections")
122
+
123
+ config_data = extract_environment_data(configurations, environment)
124
+ return extract_resources(config_data, collection)
125
+
126
+ except Exception as e:
127
+ raise Exception(f"Extraction of configurations failed with error:\n {str(e)}")
128
+
129
+
130
+ def format_config(res: Dict[str, List[Any]], environment_id: str, collection_id: str) -> str:
131
+ """
132
+ Formats the extracted resources into unified config format.
133
+
134
+ :param res: The extracted config (from `extract_configurations`).
135
+ :param environment_id: The environment ID to include.
136
+ :param collection_id: The collection ID to include.
137
+ :return: A formatted configuration dictionary.
138
+ """
139
+ return json.dumps({
140
+ "environments": [
141
+ {
142
+ "environment_id": environment_id,
143
+ "features": res.get("features", []),
144
+ "properties": res.get("properties", [])
145
+ }
146
+ ],
147
+ "collections": [{"collection_id": collection_id}],
148
+ "segments": res.get("segments", [])
149
+ })
@@ -84,12 +84,12 @@ class URLBuilder:
84
84
  cls.__iam_url = "https://iam.cloud.ibm.com"
85
85
  cls.__web_socket_base = cls.__wss + region + cls.__base_url
86
86
 
87
- cls.__config_path = '{0}{1}{2}/collections/{3}/{4}?environment_id={5}'.format(
87
+ cls.__config_path = '{0}{1}{2}/{3}?action=sdkConfig&collection_id={4}&environment_id={5}'.format(
88
88
  cls.__service,
89
89
  cls.__feature_path,
90
90
  guid,
91
- collection_id,
92
91
  cls.__config,
92
+ collection_id,
93
93
  environment_id)
94
94
  cls.__metering_path = '{0}{1}{2}/usage'.format(cls.__service,
95
95
  cls.__events_path,
@@ -45,18 +45,30 @@ class Rule:
45
45
  def __ends_with(self, key, value) -> bool:
46
46
  return key.endswith(value)
47
47
 
48
+ def __not_ends_with(self, key, value) -> bool:
49
+ return not key.endswith(value)
50
+
48
51
  def __starts_with(self, key, value) -> bool:
49
52
  return key.startswith(value)
50
53
 
54
+ def __not_starts_with(self, key, value) -> bool:
55
+ return not key.startswith(value)
56
+
51
57
  def __contains(self, key, value) -> bool:
52
58
  return value in key
53
59
 
60
+ def __not_contains(self, key, value) -> bool:
61
+ return not value in key
62
+
54
63
  def __is(self, key, value) -> bool:
55
64
  if type(key) is type(value):
56
65
  return key == value
57
- if str(key) == str(value):
58
- return True
59
- return False
66
+ return str(key) == str(value)
67
+
68
+ def __is_not(self, key, value) -> bool:
69
+ if type(key) is type(value):
70
+ return key != value
71
+ return str(key) != str(value)
60
72
 
61
73
  def __greater_than(self, key, value) -> bool:
62
74
 
@@ -102,9 +114,13 @@ class Rule:
102
114
 
103
115
  case_checker = {
104
116
  "endsWith": self.__ends_with,
117
+ "notEndsWith": self.__not_ends_with,
105
118
  "startsWith": self.__starts_with,
119
+ "notStartsWith": self.__not_starts_with,
106
120
  "contains": self.__contains,
121
+ "notContains": self.__not_contains,
107
122
  "is": self.__is,
123
+ "isNot": self.__is_not,
108
124
  "greaterThan": self.__greater_than,
109
125
  "lesserThan": self.__lesser_than,
110
126
  "greaterThanEquals": self.__greater_than_equals,
@@ -130,9 +146,17 @@ class Rule:
130
146
  key = entity_attributes.get(self.__attribute_name)
131
147
  else:
132
148
  return result
133
- for i in range(0, len(self.__values)):
134
- value = self.__values[i]
135
- if self.__operator_check(key, value):
136
- result = True
149
+
150
+ if self.__operator in ["isNot", "notContains", "notStartsWith", "notEndsWith"]:
151
+ result = True
152
+ for i in range(0, len(self.__values)):
153
+ value = self.__values[i]
154
+ if not self.__operator_check(key, value):
155
+ result = False
156
+ else:
157
+ for i in range(0, len(self.__values)):
158
+ value = self.__values[i]
159
+ if self.__operator_check(key, value):
160
+ result = True
137
161
 
138
162
  return result
@@ -15,4 +15,4 @@
15
15
  """
16
16
  Version of ibm-appconfiguration-python-sdk
17
17
  """
18
- __version__ = '0.3.7'
18
+ __version__ = '0.3.9'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ibm-appconfiguration-python-sdk
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary: IBM Cloud App Configuration Python SDK
5
5
  Home-page: https://github.com/IBM/appconfiguration-python-sdk
6
6
  Author: IBM
@@ -21,6 +21,18 @@ Requires-Dist: ibm-cloud-sdk-core<4.0.0,>=3.20.3
21
21
  Requires-Dist: pyyaml>=5.4.1
22
22
  Requires-Dist: schema>=0.7.5
23
23
  Requires-Dist: mmh3==5.0.1
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: keywords
31
+ Dynamic: license
32
+ Dynamic: license-file
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
24
36
 
25
37
  # IBM Cloud App Configuration Python server SDK
26
38
 
@@ -74,15 +86,7 @@ The **`init()`** and **`set_context()`** are the initialisation methods and shou
74
86
  appconfig_client. The appconfig_client, once initialised, can be obtained across modules
75
87
  using **`AppConfiguration.get_instance()`**. [See this example below](#fetching-the-appconfig_client-across-other-modules).
76
88
 
77
- - region : Region name where the App Configuration service instance is created. Use
78
- - `AppConfiguration.REGION_US_SOUTH` for Dallas
79
- - `AppConfiguration.REGION_EU_GB` for London
80
- - `AppConfiguration.REGION_AU_SYD` for Sydney
81
- - `AppConfiguration.REGION_US_EAST` for Washington DC
82
- - `AppConfiguration.REGION_EU_DE` for Frankfurt
83
- - `AppConfiguration.REGION_CA_TOR` for Toronto
84
- - `AppConfiguration.REGION_JP_TOK` for Tokyo
85
- - `AppConfiguration.REGION_JP_OSA` for Osaka
89
+ - region : Region name where the App Configuration service instance is created. See list of supported locations [here](https://cloud.ibm.com/catalog/services/app-configuration). Eg:- `us-south`, `au-syd` etc.
86
90
  - guid : GUID of the App Configuration service. Obtain it from the service credentials section of the dashboard
87
91
  - apikey : ApiKey of the App Configuration service. Obtain it from the service credentials section of the dashboard
88
92
  - collection_id : Id of the collection created in App Configuration service instance under the **Collections** section.
@@ -20,6 +20,7 @@ ibm_appconfiguration/configurations/internal/utils/connectivity.py
20
20
  ibm_appconfiguration/configurations/internal/utils/file_manager.py
21
21
  ibm_appconfiguration/configurations/internal/utils/logger.py
22
22
  ibm_appconfiguration/configurations/internal/utils/metering.py
23
+ ibm_appconfiguration/configurations/internal/utils/parser.py
23
24
  ibm_appconfiguration/configurations/internal/utils/socket.py
24
25
  ibm_appconfiguration/configurations/internal/utils/url_builder.py
25
26
  ibm_appconfiguration/configurations/internal/utils/validators.py
@@ -12,7 +12,7 @@
12
12
  from setuptools import setup, find_packages
13
13
 
14
14
  NAME = "ibm-appconfiguration-python-sdk"
15
- VERSION = "0.3.7"
15
+ VERSION = "0.3.9"
16
16
  # To install the library, run the following
17
17
  #
18
18
  # python setup.py install
@@ -56,6 +56,17 @@ class MyTestCase(unittest.TestCase):
56
56
  }
57
57
  self.assertFalse(self.sut.evaluate_rule(client_attributes))
58
58
 
59
+ def test_evaluation_not_ends_with_string(self):
60
+ self.set_up_email('google.com', 'email', 'notEndsWith')
61
+ client_attributes = {
62
+ 'email': 'tester@in.ibm.com'
63
+ }
64
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
65
+ client_attributes = {
66
+ 'email': 'tester@google.com'
67
+ }
68
+ self.assertFalse(self.sut.evaluate_rule(client_attributes))
69
+
59
70
  def test_evaluation_is(self):
60
71
  self.set_up_email("123", "creditValues", "is")
61
72
  client_attributes = {
@@ -99,6 +110,31 @@ class MyTestCase(unittest.TestCase):
99
110
  self.set_up_email(False, "creditValues", "is")
100
111
  self.assertTrue(self.sut.evaluate_rule(client_attributes))
101
112
 
113
+ def test_evaluation_is_not(self):
114
+ self.set_up_email("123", "creditValues", "isNot")
115
+ client_attributes = {
116
+ 'creditValues': '1234'
117
+ }
118
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
119
+
120
+ self.set_up_email("1234", "creditValues", "isNot")
121
+ client_attributes = {
122
+ 'creditValues': 123
123
+ }
124
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
125
+
126
+ client_attributes = {
127
+ 'creditValues': 123
128
+ }
129
+ self.set_up_email(5123, "creditValues", "isNot")
130
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
131
+
132
+ client_attributes = {
133
+ 'creditValues': False
134
+ }
135
+ self.set_up_email(True, "creditValues", "isNot")
136
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
137
+
102
138
  def test_evaluation_startswith(self):
103
139
  self.set_up_email('user1', 'email', 'startsWith')
104
140
  client_attributes = {
@@ -106,6 +142,13 @@ class MyTestCase(unittest.TestCase):
106
142
  }
107
143
  self.assertTrue(self.sut.evaluate_rule(client_attributes))
108
144
 
145
+ def test_evaluation_not_startswith(self):
146
+ self.set_up_email('user2', 'email', 'notStartsWith')
147
+ client_attributes = {
148
+ 'email': 'user1@gm.com'
149
+ }
150
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
151
+
109
152
  def test_evaluation_contains(self):
110
153
  self.set_up_email('+91', 'mobile', 'contains')
111
154
  client_attributes = {
@@ -118,6 +161,18 @@ class MyTestCase(unittest.TestCase):
118
161
  }
119
162
  self.assertFalse(self.sut.evaluate_rule(client_attributes))
120
163
 
164
+ def test_evaluation_not_contains(self):
165
+ self.set_up_email('+91', 'mobile', 'notContains')
166
+ client_attributes = {
167
+ 'mobile': '+81xxxxxxxxxx'
168
+ }
169
+ self.assertTrue(self.sut.evaluate_rule(client_attributes))
170
+
171
+ client_attributes = {
172
+ 'mobile': '+91xxxxxxxxxx'
173
+ }
174
+ self.assertFalse(self.sut.evaluate_rule(client_attributes))
175
+
121
176
  def evaluation_common(self, type, val1, val2):
122
177
  self.set_up_email(val1, 'balance', type)
123
178
  client_attributes = {
@@ -38,7 +38,7 @@ class MyTestCase(unittest.TestCase):
38
38
  'bootstrap_file': FILE,
39
39
  'live_config_update_enabled': False
40
40
  }
41
- self.sut.set_context("collectionId", "environmentId", options)
41
+ self.sut.set_context("collection", "dev", options)
42
42
  self.sut.load_data()
43
43
  time.sleep(2.5)
44
44
 
@@ -48,34 +48,44 @@ class MyTestCase(unittest.TestCase):
48
48
  def test_load_from_web(self):
49
49
  Metering.get_instance().set_repeat_calls(False)
50
50
  mock_response = '''
51
- {
52
- "features": [
53
- {
54
- "name": "featurestring",
55
- "feature_id": "featurestring",
56
- "type": "STRING",
57
- "enabled_value": "Hello",
58
- "disabled_value": "Hi",
59
- "segment_rules": [],
60
- "enabled": true
61
- }
62
- ],
63
- "properties": [
64
- {
65
- "name": "numericproperty",
66
- "property_id": "numericproperty",
67
- "tags": "",
68
- "type": "NUMERIC",
69
- "value": 30,
70
- "segment_rules": [],
71
- "created_time": "2021-05-23T08:00:56Z",
72
- "updated_time": "2021-05-23T08:00:56Z"
73
- }
74
- ],
75
- "segments": []
76
- }
51
+ {
52
+ "environments": [
53
+ {
54
+ "name": "Dev",
55
+ "environment_id": "dev",
56
+ "features": [
57
+ {
58
+ "name": "featurestring",
59
+ "feature_id": "featurestring",
60
+ "type": "STRING",
61
+ "enabled_value": "Hello",
62
+ "disabled_value": "Hi",
63
+ "segment_rules": [],
64
+ "enabled": true
65
+ }
66
+ ],
67
+ "properties": [
68
+ {
69
+ "name": "numericproperty",
70
+ "property_id": "numericproperty",
71
+ "tags": "",
72
+ "type": "NUMERIC",
73
+ "value": 30,
74
+ "segment_rules": []
75
+ }
76
+ ]
77
+ }
78
+ ],
79
+ "collections": [
80
+ {
81
+ "name": "Collection",
82
+ "collection_id": "collection"
83
+ }
84
+ ],
85
+ "segments": []
86
+ }
77
87
  '''
78
- url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id'
88
+ url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection&environment_id=dev'
79
89
  self.responses.add(responses.GET,
80
90
  url,
81
91
  body=mock_response,
@@ -87,7 +97,7 @@ class MyTestCase(unittest.TestCase):
87
97
  'bootstrap_file': None,
88
98
  'live_config_update_enabled': True
89
99
  }
90
- self.sut.set_context("collection_id", "environment_id", options)
100
+ self.sut.set_context("collection", "dev", options)
91
101
  self.sut.load_data()
92
102
  features = self.sut.get_features()
93
103
  self.assertEqual(len(features), 1)
@@ -178,54 +188,63 @@ class MyTestCase(unittest.TestCase):
178
188
  Metering.get_instance().set_repeat_calls(False)
179
189
  mock_response = '''
180
190
  {
181
- "features": [
191
+ "environments": [
182
192
  {
183
- "name": "yamlFeature",
184
- "feature_id": "yamlFeature",
185
- "type": "STRING",
186
- "format": "YAML",
187
- "enabled_value": "value: enabled",
188
- "disabled_value": "value: disabled",
189
- "segment_rules": [
193
+ "name": "Dev",
194
+ "environment_id": "dev",
195
+ "features": [
190
196
  {
191
- "rules": [
197
+ "name": "yamlFeature",
198
+ "feature_id": "yamlFeature",
199
+ "type": "STRING",
200
+ "format": "YAML",
201
+ "enabled_value": "value: enabled",
202
+ "disabled_value": "value: disabled",
203
+ "segment_rules": [
192
204
  {
193
- "segments": [
194
- "reqbody"
195
- ]
205
+ "rules": [
206
+ {
207
+ "segments": [
208
+ "reqbody"
209
+ ]
210
+ }
211
+ ],
212
+ "value": "value: targeted",
213
+ "order": 1
196
214
  }
197
215
  ],
198
- "value": "value: targeted",
199
- "order": 1
216
+ "enabled": true
200
217
  }
201
218
  ],
202
- "enabled": true
203
- }
204
- ],
205
- "properties": [
206
- {
207
- "name": "yamlProperty",
208
- "property_id": "yamlProperty",
209
- "tags": "",
210
- "type": "STRING",
211
- "format": "YAML",
212
- "value": "value: enabled",
213
- "segment_rules": [
219
+ "properties": [
214
220
  {
215
- "rules": [
221
+ "name": "yamlProperty",
222
+ "property_id": "yamlProperty",
223
+ "tags": "",
224
+ "type": "STRING",
225
+ "format": "YAML",
226
+ "value": "value: enabled",
227
+ "segment_rules": [
216
228
  {
217
- "segments": [
218
- "reqbody"
219
- ]
229
+ "rules": [
230
+ {
231
+ "segments": [
232
+ "reqbody"
233
+ ]
234
+ }
235
+ ],
236
+ "value": "value: targeted",
237
+ "order": 1
220
238
  }
221
- ],
222
- "value": "value: targeted",
223
- "order": 1
239
+ ]
224
240
  }
225
-
226
- ],
227
- "created_time": "2021-05-23T08:00:56Z",
228
- "updated_time": "2021-05-23T08:00:56Z"
241
+ ]
242
+ }
243
+ ],
244
+ "collections": [
245
+ {
246
+ "name": "Collection",
247
+ "collection_id": "collection"
229
248
  }
230
249
  ],
231
250
  "segments": [
@@ -245,7 +264,7 @@ class MyTestCase(unittest.TestCase):
245
264
  ]
246
265
  }
247
266
  '''
248
- url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id'
267
+ url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection&environment_id=dev'
249
268
  self.responses.add(responses.GET,
250
269
  url,
251
270
  body=mock_response,
@@ -257,7 +276,7 @@ class MyTestCase(unittest.TestCase):
257
276
  'bootstrap_file': None,
258
277
  'live_config_update_enabled': True
259
278
  }
260
- self.sut.set_context("collection_id", "environment_id", options)
279
+ self.sut.set_context("collection", "dev", options)
261
280
  self.sut.load_data()
262
281
  features = self.sut.get_features()
263
282
  properties = self.sut.get_properties()
@@ -41,8 +41,19 @@ class MyTestCase(unittest.TestCase):
41
41
  self.api_manager = APIManager.get_instance()
42
42
 
43
43
  def test_get_call(self):
44
- mock_response = '{ "features": [], "properties": [], "segments": []}'
45
- url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id'
44
+ mock_response = '''
45
+ {
46
+ "environments": [
47
+ {
48
+ "features": [],
49
+ "properties": []
50
+ }
51
+ ],
52
+ "collections": [],
53
+ "segments": []
54
+ }
55
+ '''
56
+ url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id'
46
57
  self.responses.add(responses.GET,
47
58
  url,
48
59
  body=mock_response,
@@ -50,7 +61,7 @@ class MyTestCase(unittest.TestCase):
50
61
  status=200)
51
62
 
52
63
  resp = self.api_manager.prepare_api_request(method="GET", url=URLBuilder.get_config_path())
53
- self.assertEqual(resp.get_status_code(), 200)
64
+ self.assertEqual(200, resp.get_status_code())
54
65
 
55
66
  try:
56
67
  response_data = dict(resp.get_result())
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
14
+ import json
15
15
  import unittest
16
16
  import os
17
17
  from ibm_appconfiguration.configurations.internal.utils.file_manager import FileManager
@@ -69,7 +69,7 @@ class MyTestCase(unittest.TestCase):
69
69
  }
70
70
  ]
71
71
  }
72
- self.assertTrue(FileManager.store_files(data, self.file_path))
72
+ self.assertTrue(FileManager.store_files(json.dumps(data), self.file_path))
73
73
 
74
74
  expected_data = FileManager.read_files(self.file_path)
75
75
 
@@ -31,7 +31,7 @@ class MyTestCase(unittest.TestCase):
31
31
  self.assertEqual(URLBuilder.get_base_url(), 'https://region.apprapp.cloud.ibm.com')
32
32
  self.assertEqual(URLBuilder.get_iam_url(), 'https://iam.cloud.ibm.com')
33
33
  self.assertEqual(URLBuilder.get_config_path(),
34
- '/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id')
34
+ '/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
35
35
  self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
36
36
  self.assertEqual(URLBuilder.get_web_socket_url(),
37
37
  'wss://region.apprapp.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
@@ -48,7 +48,7 @@ class MyTestCase(unittest.TestCase):
48
48
  self.assertEqual(URLBuilder.get_base_url(), 'https://private.region.apprapp.cloud.ibm.com')
49
49
  self.assertEqual(URLBuilder.get_iam_url(), 'https://private.iam.cloud.ibm.com')
50
50
  self.assertEqual(URLBuilder.get_config_path(),
51
- '/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id')
51
+ '/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
52
52
  self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
53
53
  self.assertEqual(URLBuilder.get_web_socket_url(),
54
54
  'wss://private.region.apprapp.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
@@ -65,7 +65,7 @@ class MyTestCase(unittest.TestCase):
65
65
  self.assertEqual(URLBuilder.get_base_url(), 'https://region.apprapp.test.cloud.ibm.com')
66
66
  self.assertEqual(URLBuilder.get_iam_url(), 'https://iam.test.cloud.ibm.com')
67
67
  self.assertEqual(URLBuilder.get_config_path(),
68
- '/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id')
68
+ '/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
69
69
  self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
70
70
  self.assertEqual(URLBuilder.get_web_socket_url(),
71
71
  'wss://region.apprapp.test.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
@@ -82,7 +82,7 @@ class MyTestCase(unittest.TestCase):
82
82
  self.assertEqual(URLBuilder.get_base_url(), 'https://private.region.apprapp.test.cloud.ibm.com')
83
83
  self.assertEqual(URLBuilder.get_iam_url(), 'https://private.iam.test.cloud.ibm.com')
84
84
  self.assertEqual(URLBuilder.get_config_path(),
85
- '/apprapp/feature/v1/instances/guid/collections/collection_id/config?environment_id=environment_id')
85
+ '/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
86
86
  self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
87
87
  self.assertEqual(URLBuilder.get_web_socket_url(),
88
88
  'wss://private.region.apprapp.test.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
@@ -72,7 +72,7 @@ class MyTestCase(unittest.TestCase):
72
72
  sut1.enable_debug(True)
73
73
 
74
74
  FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user.json')
75
- sut1.set_context("collectionId", "environmentId", configuration_file=FILE, live_config_update_enabled=False)
75
+ sut1.set_context("collection", "dev", configuration_file=FILE, live_config_update_enabled=False)
76
76
  time.sleep(2.5)
77
77
 
78
78
  self.assertEqual(len(sut1.get_features()), 3)