devcycle-python-server-sdk 3.13.0__tar.gz → 3.13.2__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 (84) hide show
  1. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/LICENSE +1 -1
  2. {devcycle_python_server_sdk-3.13.0/devcycle_python_server_sdk.egg-info → devcycle_python_server_sdk-3.13.2}/PKG-INFO +2 -2
  3. devcycle_python_server_sdk-3.13.2/devcycle_python_sdk/VERSION.txt +1 -0
  4. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/managers/config_manager.py +5 -5
  5. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/user.py +3 -3
  6. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/open_feature_provider/provider.py +7 -5
  7. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2/devcycle_python_server_sdk.egg-info}/PKG-INFO +2 -2
  8. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_server_sdk.egg-info/requires.txt +1 -1
  9. devcycle_python_server_sdk-3.13.2/example/openfeature_example.py +238 -0
  10. devcycle_python_server_sdk-3.13.0/devcycle_python_sdk/VERSION.txt +0 -1
  11. devcycle_python_server_sdk-3.13.0/example/openfeature_example.py +0 -77
  12. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/README.md +0 -0
  13. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/__init__.py +0 -0
  14. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/api/__init__.py +0 -0
  15. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/api/backoff.py +0 -0
  16. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/api/bucketing_client.py +0 -0
  17. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/api/config_client.py +0 -0
  18. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/api/event_client.py +0 -0
  19. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/api/local_bucketing.py +0 -0
  20. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
  21. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/cloud_client.py +0 -0
  22. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/devcycle_client.py +0 -0
  23. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/exceptions.py +0 -0
  24. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/local_client.py +0 -0
  25. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/managers/__init__.py +0 -0
  26. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/managers/eval_hooks_manager.py +0 -0
  27. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/managers/event_queue_manager.py +0 -0
  28. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/managers/sse_manager.py +0 -0
  29. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/__init__.py +0 -0
  30. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/bucketed_config.py +0 -0
  31. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/config_metadata.py +0 -0
  32. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/environment_metadata.py +0 -0
  33. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/error_response.py +0 -0
  34. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/eval_hook.py +0 -0
  35. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/eval_hook_context.py +0 -0
  36. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/eval_reason.py +0 -0
  37. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/event.py +0 -0
  38. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/feature.py +0 -0
  39. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/platform_data.py +0 -0
  40. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/project_metadata.py +0 -0
  41. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/variable.py +0 -0
  42. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/models/variable_metadata.py +0 -0
  43. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/open_feature_provider/__init__.py +0 -0
  44. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/options.py +0 -0
  45. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/protobuf/__init__.py +0 -0
  46. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/protobuf/utils.py +0 -0
  47. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +0 -0
  48. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +0 -0
  49. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/py.typed +0 -0
  50. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/util/__init__.py +0 -0
  51. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/util/strings.py +0 -0
  52. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_sdk/util/version.py +0 -0
  53. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_server_sdk.egg-info/SOURCES.txt +0 -0
  54. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_server_sdk.egg-info/dependency_links.txt +0 -0
  55. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/devcycle_python_server_sdk.egg-info/top_level.txt +0 -0
  56. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/example/__init__.py +0 -0
  57. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/example/cloud_client_example.py +0 -0
  58. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/example/local_bucketing_client_example.py +0 -0
  59. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/pyproject.toml +0 -0
  60. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/setup.cfg +0 -0
  61. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/setup.py +0 -0
  62. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/__init__.py +0 -0
  63. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/api/__init__.py +0 -0
  64. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/api/test_bucketing_client.py +0 -0
  65. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/api/test_config_client.py +0 -0
  66. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/api/test_event_client.py +0 -0
  67. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/api/test_local_bucketing.py +0 -0
  68. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/fixture/__init__.py +0 -0
  69. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/fixture/data.py +0 -0
  70. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/managers/__init__.py +0 -0
  71. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/managers/test_config_manager.py +0 -0
  72. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/managers/test_event_queue_manager.py +0 -0
  73. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/models/__init__.py +0 -0
  74. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/models/test_bucketed_config.py +0 -0
  75. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/models/test_user.py +0 -0
  76. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/openfeature_test/__init__.py +0 -0
  77. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/openfeature_test/test_provider.py +0 -0
  78. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/openfeature_test/test_provider_local_sdk.py +0 -0
  79. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/test_cloud_client.py +0 -0
  80. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/test_local_client.py +0 -0
  81. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/util/__init__.py +0 -0
  82. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/util/test_strings.py +0 -0
  83. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/util/test_utils.py +0 -0
  84. {devcycle_python_server_sdk-3.13.0 → devcycle_python_server_sdk-3.13.2}/test/util/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Taplytics Inc.
3
+ Copyright (c) 2025 Taplytics Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devcycle-python-server-sdk
3
- Version: 3.13.0
3
+ Version: 3.13.2
4
4
  Summary: DevCycle Python SDK
5
5
  Home-page: https://github.com/devcycleHQ/python-server-sdk
6
6
  Author-email:
@@ -12,7 +12,7 @@ Requires-Dist: urllib3>=1.15.1
12
12
  Requires-Dist: requests>=2.32
13
13
  Requires-Dist: wasmtime~=30.0.0
14
14
  Requires-Dist: protobuf>=4.23.3
15
- Requires-Dist: openfeature-sdk==0.8.0
15
+ Requires-Dist: openfeature-sdk~=0.8.0
16
16
  Requires-Dist: launchdarkly-eventsource>=1.2.1
17
17
  Requires-Dist: responses>=0.23.1
18
18
  Dynamic: description
@@ -10,8 +10,8 @@ import ld_eventsource.actions
10
10
  from devcycle_python_sdk.api.config_client import ConfigAPIClient
11
11
  from devcycle_python_sdk.api.local_bucketing import LocalBucketing
12
12
  from devcycle_python_sdk.exceptions import (
13
- CloudClientUnauthorizedError,
14
- CloudClientError,
13
+ APIClientError,
14
+ APIClientUnauthorizedError,
15
15
  )
16
16
  from wsgiref.handlers import format_date_time
17
17
  from devcycle_python_sdk.options import DevCycleLocalOptions
@@ -101,11 +101,11 @@ class EnvironmentConfigManager(threading.Thread):
101
101
  logger.warning(
102
102
  f"DevCycle: Error received from on_client_initialized callback: {str(e)}"
103
103
  )
104
- except CloudClientError as e:
104
+ except APIClientError as e:
105
105
  logger.warning(f"DevCycle: Config fetch failed. Status: {str(e)}")
106
- except CloudClientUnauthorizedError:
106
+ except APIClientUnauthorizedError:
107
107
  logger.error(
108
- "DevCycle: Unauthorized to get config. Aborting config polling."
108
+ "DevCycle: Error initializing DevCycle: Invalid SDK key provided."
109
109
  )
110
110
  self._polling_enabled = False
111
111
 
@@ -1,7 +1,7 @@
1
1
  # ruff: noqa: N815
2
2
  from dataclasses import dataclass, field
3
3
  from datetime import datetime, timezone
4
- from typing import Dict, Optional, Any
4
+ from typing import Dict, Optional, Any, cast
5
5
  from openfeature.evaluation_context import EvaluationContext
6
6
  from openfeature.exception import TargetingKeyMissingError, InvalidContextError
7
7
 
@@ -114,10 +114,10 @@ class DevCycleUser:
114
114
  user_id = context.targeting_key
115
115
  user_id_source = "targeting_key"
116
116
  elif context.attributes and "user_id" in context.attributes.keys():
117
- user_id = context.attributes["user_id"]
117
+ user_id = cast(str, context.attributes["user_id"])
118
118
  user_id_source = "user_id"
119
119
  elif context.attributes and "userId" in context.attributes.keys():
120
- user_id = context.attributes["userId"]
120
+ user_id = cast(str, context.attributes["userId"])
121
121
  user_id_source = "userId"
122
122
 
123
123
  if not user_id:
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  import time
3
3
 
4
- from typing import Any, Optional, Union, List
4
+ from typing import Any, Optional, Union, List, Mapping, Sequence
5
5
 
6
6
  from devcycle_python_sdk import AbstractDevCycleClient
7
7
  from devcycle_python_sdk.models.user import DevCycleUser
@@ -9,7 +9,7 @@ from devcycle_python_sdk.models.user import DevCycleUser
9
9
  from openfeature.provider import AbstractProvider
10
10
  from openfeature.provider.metadata import Metadata
11
11
  from openfeature.evaluation_context import EvaluationContext
12
- from openfeature.flag_evaluation import FlagResolutionDetails, Reason
12
+ from openfeature.flag_evaluation import FlagResolutionDetails, Reason, FlagValueType
13
13
  from openfeature.exception import (
14
14
  ErrorCode,
15
15
  InvalidContextError,
@@ -138,10 +138,12 @@ class DevCycleProvider(AbstractProvider):
138
138
  def resolve_object_details(
139
139
  self,
140
140
  flag_key: str,
141
- default_value: Union[dict, list],
141
+ default_value: Union[Mapping[str, FlagValueType], Sequence[FlagValueType]],
142
142
  evaluation_context: Optional[EvaluationContext] = None,
143
- ) -> FlagResolutionDetails[Union[dict, list]]:
144
- if not isinstance(default_value, dict):
143
+ ) -> FlagResolutionDetails[
144
+ Union[Mapping[str, FlagValueType], Sequence[FlagValueType]]
145
+ ]:
146
+ if not isinstance(default_value, Mapping):
145
147
  raise TypeMismatchError("Default value must be a flat dictionary")
146
148
 
147
149
  if default_value:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devcycle-python-server-sdk
3
- Version: 3.13.0
3
+ Version: 3.13.2
4
4
  Summary: DevCycle Python SDK
5
5
  Home-page: https://github.com/devcycleHQ/python-server-sdk
6
6
  Author-email:
@@ -12,7 +12,7 @@ Requires-Dist: urllib3>=1.15.1
12
12
  Requires-Dist: requests>=2.32
13
13
  Requires-Dist: wasmtime~=30.0.0
14
14
  Requires-Dist: protobuf>=4.23.3
15
- Requires-Dist: openfeature-sdk==0.8.0
15
+ Requires-Dist: openfeature-sdk~=0.8.0
16
16
  Requires-Dist: launchdarkly-eventsource>=1.2.1
17
17
  Requires-Dist: responses>=0.23.1
18
18
  Dynamic: description
@@ -3,6 +3,6 @@ urllib3>=1.15.1
3
3
  requests>=2.32
4
4
  wasmtime~=30.0.0
5
5
  protobuf>=4.23.3
6
- openfeature-sdk==0.8.0
6
+ openfeature-sdk~=0.8.0
7
7
  launchdarkly-eventsource>=1.2.1
8
8
  responses>=0.23.1
@@ -0,0 +1,238 @@
1
+ import logging
2
+ import time
3
+ import os
4
+
5
+ from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
6
+
7
+ from openfeature import api
8
+ from openfeature.evaluation_context import EvaluationContext
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def main():
14
+ """
15
+ Sample usage of the DevCycle OpenFeature Provider along with the Python Server SDK using Local Bucketing.
16
+
17
+ This example demonstrates how to use all variable types supported by DevCycle through OpenFeature:
18
+ - Boolean variables
19
+ - String variables
20
+ - Number variables (integer and float)
21
+ - JSON object variables
22
+
23
+ See DEVCYCLE_SETUP.md for instructions on creating the required variables in DevCycle.
24
+ """
25
+ logging.basicConfig(level="INFO", format="%(levelname)s: %(message)s")
26
+
27
+ # create an instance of the DevCycle Client object
28
+ server_sdk_key = os.environ.get("DEVCYCLE_SERVER_SDK_KEY")
29
+ if not server_sdk_key:
30
+ logger.error("DEVCYCLE_SERVER_SDK_KEY environment variable is not set")
31
+ logger.error(
32
+ "Please set it with: export DEVCYCLE_SERVER_SDK_KEY='your-sdk-key'"
33
+ )
34
+ exit(1)
35
+
36
+ devcycle_client = DevCycleLocalClient(server_sdk_key, DevCycleLocalOptions())
37
+
38
+ # Wait for DevCycle to initialize and load the configuration
39
+ for i in range(10):
40
+ if devcycle_client.is_initialized():
41
+ break
42
+ logger.info("Waiting for DevCycle to initialize...")
43
+ time.sleep(0.5)
44
+ else:
45
+ logger.error("DevCycle failed to initialize")
46
+ exit(1)
47
+
48
+ logger.info("DevCycle initialized successfully!\n")
49
+
50
+ # set the provider for OpenFeature
51
+ api.set_provider(devcycle_client.get_openfeature_provider())
52
+
53
+ # get the OpenFeature client
54
+ open_feature_client = api.get_client()
55
+
56
+ # create the request context for the user
57
+ context = EvaluationContext(
58
+ targeting_key="test-1234",
59
+ attributes={
60
+ "email": "test-user@domain.com",
61
+ "name": "Test User",
62
+ "language": "en",
63
+ "country": "CA",
64
+ "appVersion": "1.0.11",
65
+ "appBuild": 1000,
66
+ "customData": {"custom": "data"},
67
+ "privateCustomData": {"private": "data"},
68
+ },
69
+ )
70
+
71
+ logger.info("=" * 60)
72
+ logger.info("Testing Boolean Variable")
73
+ logger.info("=" * 60)
74
+
75
+ # Test Boolean Variable
76
+ boolean_details = open_feature_client.get_boolean_details(
77
+ "test-boolean-variable", False, context
78
+ )
79
+ logger.info("Variable Key: test-boolean-variable")
80
+ logger.info("Value: {boolean_details.value}")
81
+ logger.info("Reason: {boolean_details.reason}")
82
+ if boolean_details.value:
83
+ logger.info("✓ Boolean variable is ENABLED")
84
+ else:
85
+ logger.info("✗ Boolean variable is DISABLED")
86
+
87
+ logger.info("\n" + "=" * 60)
88
+ logger.info("Testing String Variable")
89
+ logger.info("=" * 60)
90
+
91
+ # Test String Variable
92
+ open_feature_client.get_string_details(
93
+ "test-string-variable", "default string", context
94
+ )
95
+ logger.info("Variable Key: test-string-variable")
96
+ logger.info("Value: {string_details.value}")
97
+ logger.info("Reason: {string_details.reason}")
98
+
99
+ logger.info("\n" + "=" * 60)
100
+ logger.info("Testing Number Variable (Integer)")
101
+ logger.info("=" * 60)
102
+
103
+ # Test Number Variable (Integer)
104
+ open_feature_client.get_integer_details("test-number-variable", 0, context)
105
+ logger.info("Variable Key: test-number-variable")
106
+ logger.info("Value: {integer_details.value}")
107
+ logger.info("Reason: {integer_details.reason}")
108
+
109
+ logger.info("\n" + "=" * 60)
110
+ logger.info("Testing Number Variable (Float)")
111
+ logger.info("=" * 60)
112
+
113
+ # Test Number Variable as Float
114
+ # Note: If the DevCycle variable is an integer, it will be cast to float
115
+ open_feature_client.get_float_value("test-number-variable", 0.0, context)
116
+ logger.info("Variable Key: test-number-variable (as float)")
117
+ logger.info("Value: {float_value}")
118
+
119
+ logger.info("\n" + "=" * 60)
120
+ logger.info("Testing JSON Object Variable")
121
+ logger.info("=" * 60)
122
+
123
+ # Test JSON Object Variable
124
+ open_feature_client.get_object_details(
125
+ "test-json-variable", {"default": "value"}, context
126
+ )
127
+ logger.info("Variable Key: test-json-variable")
128
+ logger.info("Value: {json_details.value}")
129
+ logger.info("Reason: {json_details.reason}")
130
+
131
+ logger.info("\n" + "=" * 60)
132
+ logger.info("Testing Object Variable - Empty Dictionary")
133
+ logger.info("=" * 60)
134
+
135
+ # Test with empty dictionary default (valid per OpenFeature spec)
136
+ open_feature_client.get_object_value("test-json-variable", {}, context)
137
+ logger.info("Variable Key: test-json-variable (with empty default)")
138
+ logger.info("Value: {empty_dict_result}")
139
+
140
+ logger.info("\n" + "=" * 60)
141
+ logger.info("Testing Object Variable - Mixed Types")
142
+ logger.info("=" * 60)
143
+
144
+ # Test with flat dictionary containing mixed primitive types
145
+ # OpenFeature allows string, int, float, bool, and None in flat dictionaries
146
+ mixed_default = {
147
+ "string_key": "hello",
148
+ "int_key": 42,
149
+ "float_key": 3.14,
150
+ "bool_key": True,
151
+ "none_key": None,
152
+ }
153
+ mixed_result = open_feature_client.get_object_value(
154
+ "test-json-variable", mixed_default, context
155
+ )
156
+ logger.info("Variable Key: test-json-variable (with mixed types)")
157
+ logger.info("Value: {mixed_result}")
158
+ logger.info(
159
+ f"Value types: {[(k, type(v).__name__) for k, v in mixed_result.items()]}"
160
+ )
161
+
162
+ logger.info("\n" + "=" * 60)
163
+ logger.info("Testing Object Variable - All String Values")
164
+ logger.info("=" * 60)
165
+
166
+ # Test with all string values
167
+ string_dict_default = {
168
+ "name": "John Doe",
169
+ "email": "john@example.com",
170
+ "status": "active",
171
+ }
172
+ open_feature_client.get_object_value(
173
+ "test-json-variable", string_dict_default, context
174
+ )
175
+ logger.info("Variable Key: test-json-variable (all strings)")
176
+ logger.info("Value: {string_dict_result}")
177
+
178
+ logger.info("\n" + "=" * 60)
179
+ logger.info("Testing Object Variable - Numeric Values")
180
+ logger.info("=" * 60)
181
+
182
+ # Test with numeric values (integers and floats)
183
+ numeric_dict_default = {"count": 100, "percentage": 85.5, "threshold": 0}
184
+ open_feature_client.get_object_value(
185
+ "test-json-variable", numeric_dict_default, context
186
+ )
187
+ logger.info("Variable Key: test-json-variable (numeric)")
188
+ logger.info("Value: {numeric_dict_result}")
189
+
190
+ logger.info("\n" + "=" * 60)
191
+ logger.info("Testing Object Variable - Boolean Flags")
192
+ logger.info("=" * 60)
193
+
194
+ # Test with boolean values
195
+ bool_dict_default = {"feature_a": True, "feature_b": False, "feature_c": True}
196
+ open_feature_client.get_object_value(
197
+ "test-json-variable", bool_dict_default, context
198
+ )
199
+ logger.info("Variable Key: test-json-variable (booleans)")
200
+ logger.info("Value: {bool_dict_result}")
201
+
202
+ logger.info("\n" + "=" * 60)
203
+ logger.info("Testing Object Variable - With None Values")
204
+ logger.info("=" * 60)
205
+
206
+ # Test with None values (valid per OpenFeature spec for flat dictionaries)
207
+ none_dict_default = {
208
+ "optional_field": None,
209
+ "required_field": "value",
210
+ "nullable_count": None,
211
+ }
212
+ open_feature_client.get_object_value(
213
+ "test-json-variable", none_dict_default, context
214
+ )
215
+ logger.info("Variable Key: test-json-variable (with None)")
216
+ logger.info("Value: {none_dict_result}")
217
+
218
+ logger.info("\n" + "=" * 60)
219
+ logger.info("Testing Non-Existent Variable (Should Return Default)")
220
+ logger.info("=" * 60)
221
+
222
+ # Test non-existent variable to demonstrate default handling
223
+ open_feature_client.get_string_details(
224
+ "doesnt-exist", "default fallback value", context
225
+ )
226
+ logger.info("Variable Key: doesnt-exist")
227
+ logger.info("Value: {nonexistent_details.value}")
228
+ logger.info("Reason: {nonexistent_details.reason}")
229
+
230
+ logger.info("\n" + "=" * 60)
231
+ logger.info("All tests completed!")
232
+ logger.info("=" * 60)
233
+
234
+ devcycle_client.close()
235
+
236
+
237
+ if __name__ == "__main__":
238
+ main()
@@ -1,77 +0,0 @@
1
- import logging
2
- import time
3
- import os
4
-
5
- from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
6
-
7
- from openfeature import api
8
- from openfeature.evaluation_context import EvaluationContext
9
-
10
- FLAG_KEY = "test-boolean-variable"
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- def main():
16
- """
17
- Sample usage of the DevCycle OpenFeature Provider along with the Python Server SDK using Local Bucketing.
18
- """
19
- logging.basicConfig(level="INFO", format="%(levelname)s: %(message)s")
20
-
21
- # create an instance of the DevCycle Client object
22
- server_sdk_key = os.environ["DEVCYCLE_SERVER_SDK_KEY"]
23
- devcycle_client = DevCycleLocalClient(server_sdk_key, DevCycleLocalOptions())
24
-
25
- # Wait for DevCycle to initialize and load the configuration
26
- for i in range(10):
27
- if devcycle_client.is_initialized():
28
- break
29
- logger.info("Waiting for DevCycle to initialize...")
30
- time.sleep(0.5)
31
- else:
32
- logger.error("DevCycle failed to initialize")
33
- exit(1)
34
-
35
- # set the provider for OpenFeature
36
- api.set_provider(devcycle_client.get_openfeature_provider())
37
-
38
- # get the OpenFeature client
39
- open_feature_client = api.get_client()
40
-
41
- # create the request context for the user
42
- context = EvaluationContext(
43
- targeting_key="test-1234",
44
- attributes={
45
- "email": "test-user@domain.com",
46
- "name": "Test User",
47
- "language": "en",
48
- "country": "CA",
49
- "appVersion": "1.0.11",
50
- "appBuild": 1000,
51
- "customData": {"custom": "data"},
52
- "privateCustomData": {"private": "data"},
53
- },
54
- )
55
-
56
- # Look up the value of the flag
57
- if open_feature_client.get_boolean_value(FLAG_KEY, False, context):
58
- logger.info(f"Variable {FLAG_KEY} is enabled")
59
- else:
60
- logger.info(f"Variable {FLAG_KEY} is not enabled")
61
-
62
- # Fetch a JSON object variable
63
- json_object = open_feature_client.get_object_value(
64
- "test-json-variable", {"default": "value"}, context
65
- )
66
- logger.info(f"JSON Object Value: {json_object}")
67
-
68
- # Retrieve a string variable along with resolution details
69
- details = open_feature_client.get_string_details("doesnt-exist", "default", context)
70
- logger.info(f"String Value: {details.value}")
71
- logger.info(f"Eval Reason: {details.reason}")
72
-
73
- devcycle_client.close()
74
-
75
-
76
- if __name__ == "__main__":
77
- main()