splitio-client 10.1.0rc2__tar.gz → 10.2.0__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 (124) hide show
  1. splitio_client-10.2.0/LICENSE.txt +13 -0
  2. splitio_client-10.2.0/PKG-INFO +125 -0
  3. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/client.py +235 -71
  4. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/client.py +27 -24
  5. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/factory.py +33 -26
  6. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/evaluator.py +2 -1
  7. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/impressions/__init__.py +20 -20
  8. splitio_client-10.2.0/splitio/engine/impressions/impressions.py +55 -0
  9. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/impressions.py +8 -0
  10. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/splits.py +17 -5
  11. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/recorder/recorder.py +8 -8
  12. splitio_client-10.2.0/splitio/version.py +1 -0
  13. splitio_client-10.2.0/splitio_client.egg-info/PKG-INFO +125 -0
  14. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio_client.egg-info/SOURCES.txt +1 -0
  15. splitio_client-10.1.0rc2/PKG-INFO +0 -101
  16. splitio_client-10.1.0rc2/splitio/engine/impressions/impressions.py +0 -41
  17. splitio_client-10.1.0rc2/splitio/version.py +0 -1
  18. splitio_client-10.1.0rc2/splitio_client.egg-info/PKG-INFO +0 -101
  19. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/README.md +0 -0
  20. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/setup.cfg +0 -0
  21. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/setup.py +0 -0
  22. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/__init__.py +0 -0
  23. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/__init__.py +0 -0
  24. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/auth.py +0 -0
  25. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/commons.py +0 -0
  26. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/events.py +0 -0
  27. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/impressions.py +0 -0
  28. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/segments.py +0 -0
  29. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/splits.py +0 -0
  30. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/api/telemetry.py +0 -0
  31. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/__init__.py +0 -0
  32. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/config.py +0 -0
  33. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/input_validator.py +0 -0
  34. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/key.py +0 -0
  35. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/listener.py +0 -0
  36. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/localhost.py +0 -0
  37. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/manager.py +0 -0
  38. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/client/util.py +0 -0
  39. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/__init__.py +0 -0
  40. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/cache/__init__.py +0 -0
  41. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/cache/lru.py +0 -0
  42. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/filters.py +0 -0
  43. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/hashfns/__init__.py +0 -0
  44. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/hashfns/legacy.py +0 -0
  45. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/hashfns/murmur3py.py +0 -0
  46. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/impressions/adapters.py +0 -0
  47. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/impressions/manager.py +0 -0
  48. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/impressions/strategies.py +0 -0
  49. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/impressions/unique_keys_tracker.py +0 -0
  50. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/splitters.py +0 -0
  51. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/engine/telemetry.py +0 -0
  52. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/exceptions.py +0 -0
  53. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/factories.py +0 -0
  54. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/impressions.py +0 -0
  55. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/key.py +0 -0
  56. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/__init__.py +0 -0
  57. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/datatypes.py +0 -0
  58. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/events.py +0 -0
  59. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/__init__.py +0 -0
  60. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/condition.py +0 -0
  61. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/__init__.py +0 -0
  62. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/base.py +0 -0
  63. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/keys.py +0 -0
  64. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/misc.py +0 -0
  65. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/numeric.py +0 -0
  66. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/semver.py +0 -0
  67. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/sets.py +0 -0
  68. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/string.py +0 -0
  69. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/utils/__init__.py +0 -0
  70. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/matchers/utils/utils.py +0 -0
  71. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/grammar/partitions.py +0 -0
  72. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/notification.py +0 -0
  73. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/segments.py +0 -0
  74. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/telemetry.py +0 -0
  75. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/models/token.py +0 -0
  76. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/optional/__init__.py +0 -0
  77. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/optional/loaders.py +0 -0
  78. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/__init__.py +0 -0
  79. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/manager.py +0 -0
  80. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/parser.py +0 -0
  81. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/processor.py +0 -0
  82. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/splitsse.py +0 -0
  83. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/sse.py +0 -0
  84. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/status_tracker.py +0 -0
  85. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/push/workers.py +0 -0
  86. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/recorder/__init__.py +0 -0
  87. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/spec.py +0 -0
  88. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/__init__.py +0 -0
  89. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/adapters/__init__.py +0 -0
  90. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/adapters/cache_trait.py +0 -0
  91. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/adapters/redis.py +0 -0
  92. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/adapters/util.py +0 -0
  93. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/inmemmory.py +0 -0
  94. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/pluggable.py +0 -0
  95. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/storage/redis.py +0 -0
  96. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/__init__.py +0 -0
  97. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/event.py +0 -0
  98. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/impression.py +0 -0
  99. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/manager.py +0 -0
  100. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/segment.py +0 -0
  101. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/split.py +0 -0
  102. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/synchronizer.py +0 -0
  103. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/telemetry.py +0 -0
  104. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/unique_keys.py +0 -0
  105. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/sync/util.py +0 -0
  106. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/__init__.py +0 -0
  107. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/events_sync.py +0 -0
  108. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/impressions_sync.py +0 -0
  109. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/segment_sync.py +0 -0
  110. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/split_sync.py +0 -0
  111. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/telemetry_sync.py +0 -0
  112. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/unique_keys_sync.py +0 -0
  113. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/util/__init__.py +0 -0
  114. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/util/asynctask.py +0 -0
  115. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/tasks/util/workerpool.py +0 -0
  116. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/util/__init__.py +0 -0
  117. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/util/backoff.py +0 -0
  118. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/util/decorators.py +0 -0
  119. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/util/storage_helper.py +0 -0
  120. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/util/threadutil.py +0 -0
  121. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio/util/time.py +0 -0
  122. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio_client.egg-info/dependency_links.txt +0 -0
  123. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio_client.egg-info/requires.txt +0 -0
  124. {splitio_client-10.1.0rc2 → splitio_client-10.2.0}/splitio_client.egg-info/top_level.txt +0 -0
@@ -0,0 +1,13 @@
1
+ Copyright © 2025 Split Software, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.1
2
+ Name: splitio_client
3
+ Version: 10.2.0
4
+ Summary: Split.io Python Client
5
+ Home-page: https://github.com/splitio/python-client
6
+ Download-URL: https://github.com/splitio/python-client/tarball/10.2.0
7
+ Author: Patricio Echague, Sebastian Arrubia
8
+ Author-email: pato@split.io, sebastian@split.io
9
+ License: Apache License 2.0
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 2
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Requires-Dist: requests
19
+ Requires-Dist: pyyaml
20
+ Requires-Dist: docopt>=0.6.2
21
+ Requires-Dist: enum34; python_version < "3.4"
22
+ Requires-Dist: bloom-filter2>=2.0.0
23
+ Provides-Extra: test
24
+ Requires-Dist: flake8; extra == "test"
25
+ Requires-Dist: pytest==7.0.1; extra == "test"
26
+ Requires-Dist: pytest-mock==3.11.1; extra == "test"
27
+ Requires-Dist: coverage; extra == "test"
28
+ Requires-Dist: pytest-cov==4.1.0; extra == "test"
29
+ Requires-Dist: importlib-metadata==6.7; extra == "test"
30
+ Requires-Dist: tomli==1.2.3; extra == "test"
31
+ Requires-Dist: iniconfig==1.1.1; extra == "test"
32
+ Requires-Dist: attrs==22.1.0; extra == "test"
33
+ Requires-Dist: pytest-asyncio==0.21.0; extra == "test"
34
+ Requires-Dist: aiohttp>=3.8.4; extra == "test"
35
+ Requires-Dist: aiofiles>=23.1.0; extra == "test"
36
+ Requires-Dist: requests-kerberos>=0.15.0; extra == "test"
37
+ Provides-Extra: redis
38
+ Requires-Dist: redis>=2.10.5; extra == "redis"
39
+ Provides-Extra: uwsgi
40
+ Requires-Dist: uwsgi>=2.0.0; extra == "uwsgi"
41
+ Provides-Extra: cpphash
42
+ Requires-Dist: mmh3cffi==0.2.1; extra == "cpphash"
43
+ Provides-Extra: asyncio
44
+ Requires-Dist: aiohttp>=3.8.4; extra == "asyncio"
45
+ Requires-Dist: aiofiles>=23.1.0; extra == "asyncio"
46
+ Provides-Extra: kerberos
47
+ Requires-Dist: requests-kerberos>=0.15.0; extra == "kerberos"
48
+
49
+ # Split Python SDK
50
+ ![Build Status](https://github.com/splitio/python-client/actions/workflows/ci.yml/badge.svg?branch=master)
51
+
52
+ ## Overview
53
+ This SDK is designed to work with Split, the platform for controlled rollouts, which serves features to your users via a Split feature flag to manage your complete customer experience.
54
+
55
+ [![Twitter Follow](https://img.shields.io/twitter/follow/splitsoftware.svg?style=social&label=Follow&maxAge=1529000)](https://twitter.com/intent/follow?screen_name=splitsoftware)
56
+
57
+ ## Compatibility
58
+ This SDK is compatible with **Python 3.7 and higher**.
59
+
60
+ ## Getting started
61
+ Below is a simple example that describes the instantiation and most basic usage of our SDK:
62
+
63
+ Run `pip install splitio_client`
64
+
65
+
66
+ ```python
67
+ from splitio import get_factory
68
+ from splitio.exceptions import TimeoutException
69
+ factory = get_factory('YOUR_SDK_TYPE_API_KEY', config=config)
70
+ try:
71
+ factory.block_until_ready(5) # wait up to 5 seconds
72
+ split = factory.client()
73
+ treatment = split.get_treatment('CUSTOMER_ID', 'FEATURE_FLAG_NAME')
74
+ if treatment == "on":
75
+ # insert code here to show on treatment
76
+ elif treatment == "off":
77
+ # insert code here to show off treatment
78
+ else:
79
+ # insert your control treatment code here
80
+ except TimeoutException:
81
+ # Now the user can choose whether to abort the whole execution, or just keep going
82
+ # without a ready client, which if configured properly, should become ready at some point.
83
+ pass
84
+ ```
85
+
86
+ Please refer to [our official docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) to learn about all the functionality provided by our SDK and the configuration options available for tailoring it to your current application setup.
87
+
88
+ ## Submitting issues
89
+ The Split team monitors all issues submitted to this [issue tracker](https://github.com/splitio/python-client/issues). We encourage you to use this issue tracker to submit any bug reports, feedback, and feature enhancements. We'll do our best to respond in a timely manner.
90
+
91
+ ## Contributing
92
+ Please see [Contributors Guide](CONTRIBUTORS-GUIDE.md) to find all you need to submit a Pull Request (PR).
93
+
94
+ ## License
95
+ Licensed under the Apache License, Version 2.0. See: [Apache License](http://www.apache.org/licenses/).
96
+
97
+ ## About Split
98
+
99
+ Split is the leading Feature Delivery Platform for engineering teams that want to confidently deploy features as fast as they can develop them. Split’s fine-grained management, real-time monitoring, and data-driven experimentation ensure that new features will improve the customer experience without breaking or degrading performance. Companies like Twilio, Salesforce, GoDaddy and WePay trust Split to power their feature delivery.
100
+
101
+ To learn more about Split, contact hello@split.io, or get started with feature flags for free at https://www.split.io/signup.
102
+
103
+ Split has built and maintains SDKs for:
104
+
105
+ * .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK)
106
+ * Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK)
107
+ * Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities)
108
+ * GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK)
109
+ * iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK)
110
+ * Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK)
111
+ * JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK)
112
+ * JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK)
113
+ * Node [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK)
114
+ * PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK)
115
+ * Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK)
116
+ * React [Github](https://github.com/splitio/react-client) [Docs](https://help.split.io/hc/en-us/articles/360038825091-React-SDK)
117
+ * React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK)
118
+ * Redux [Github](https://github.com/splitio/redux-client) [Docs](https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK)
119
+ * Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK)
120
+
121
+ For a comprehensive list of open source projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20).
122
+
123
+ **Learn more about Split:**
124
+
125
+ Visit [split.io/product](https://www.split.io/product) for an overview of Split, or visit our documentation at [help.split.io](http://help.split.io) for more detailed information.
@@ -19,7 +19,7 @@ AUTH_URL = 'https://auth.split.io/api'
19
19
  TELEMETRY_URL = 'https://telemetry.split.io/api'
20
20
 
21
21
  _LOGGER = logging.getLogger(__name__)
22
-
22
+ _EXC_MSG = '{source} library is throwing exceptions'
23
23
 
24
24
  HttpResponse = namedtuple('HttpResponse', ['status_code', 'body', 'headers'])
25
25
 
@@ -119,10 +119,27 @@ class HttpClientBase(object, metaclass=abc.ABCMeta):
119
119
  headers.update(extra_headers)
120
120
  return headers
121
121
 
122
+ def _record_telemetry(self, status_code, elapsed):
123
+ """
124
+ Record Telemetry info
125
+
126
+ :param status_code: http request status code
127
+ :type status_code: int
128
+
129
+ :param elapsed: response time elapsed.
130
+ :type status_code: int
131
+ """
132
+ self._telemetry_runtime_producer.record_sync_latency(self._metric_name, elapsed)
133
+ if 200 <= status_code < 300:
134
+ self._telemetry_runtime_producer.record_successful_sync(self._metric_name, get_current_epoch_time_ms())
135
+ return
136
+
137
+ self._telemetry_runtime_producer.record_sync_error(self._metric_name, status_code)
138
+
122
139
  class HttpClient(HttpClientBase):
123
140
  """HttpClient wrapper."""
124
141
 
125
- def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, authentication_scheme=None, authentication_params=None):
142
+ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None):
126
143
  """
127
144
  Class constructor.
128
145
 
@@ -140,9 +157,6 @@ class HttpClient(HttpClientBase):
140
157
  _LOGGER.debug("Initializing httpclient")
141
158
  self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
142
159
  self._urls = _construct_urls(sdk_url, events_url, auth_url, telemetry_url)
143
- self._authentication_scheme = authentication_scheme
144
- self._authentication_params = authentication_params
145
- self._lock = threading.RLock()
146
160
 
147
161
  def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments
148
162
  """
@@ -162,22 +176,19 @@ class HttpClient(HttpClientBase):
162
176
  :return: Tuple of status_code & response text
163
177
  :rtype: HttpResponse
164
178
  """
165
- with self._lock:
166
- start = get_current_epoch_time_ms()
167
- with requests.Session() as session:
168
- self._set_authentication(session)
169
- try:
170
- response = session.get(
171
- _build_url(server, path, self._urls),
172
- params=query,
173
- headers=self._get_headers(extra_headers, sdk_key),
174
- timeout=self._timeout
175
- )
176
- self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
177
- return HttpResponse(response.status_code, response.text, response.headers)
178
-
179
- except Exception as exc: # pylint: disable=broad-except
180
- raise HttpClientException('requests library is throwing exceptions') from exc
179
+ start = get_current_epoch_time_ms()
180
+ try:
181
+ response = requests.get(
182
+ _build_url(server, path, self._urls),
183
+ params=query,
184
+ headers=self._get_headers(extra_headers, sdk_key),
185
+ timeout=self._timeout
186
+ )
187
+ self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
188
+ return HttpResponse(response.status_code, response.text, response.headers)
189
+
190
+ except Exception as exc: # pylint: disable=broad-except
191
+ raise HttpClientException(_EXC_MSG.format(source='request')) from exc
181
192
 
182
193
  def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
183
194
  """
@@ -199,54 +210,19 @@ class HttpClient(HttpClientBase):
199
210
  :return: Tuple of status_code & response text
200
211
  :rtype: HttpResponse
201
212
  """
202
- with self._lock:
203
- start = get_current_epoch_time_ms()
204
- with requests.Session() as session:
205
- self._set_authentication(session)
206
- try:
207
- response = session.post(
208
- _build_url(server, path, self._urls),
209
- json=body,
210
- params=query,
211
- headers=self._get_headers(extra_headers, sdk_key),
212
- timeout=self._timeout,
213
- )
214
- self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
215
- return HttpResponse(response.status_code, response.text, response.headers)
216
- except Exception as exc: # pylint: disable=broad-except
217
- raise HttpClientException('requests library is throwing exceptions') from exc
218
-
219
- def _set_authentication(self, session):
220
- if self._authentication_scheme == AuthenticateScheme.KERBEROS_SPNEGO:
221
- _LOGGER.debug("Using Kerberos Spnego Authentication")
222
- if self._authentication_params != [None, None]:
223
- session.auth = HTTPKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1], mutual_authentication=OPTIONAL)
224
- else:
225
- session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
226
- elif self._authentication_scheme == AuthenticateScheme.KERBEROS_PROXY:
227
- _LOGGER.debug("Using Kerberos Proxy Authentication")
228
- if self._authentication_params != [None, None]:
229
- session.mount('https://', HTTPAdapterWithProxyKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1]))
230
- else:
231
- session.mount('https://', HTTPAdapterWithProxyKerberosAuth())
232
-
233
-
234
- def _record_telemetry(self, status_code, elapsed):
235
- """
236
- Record Telemetry info
237
-
238
- :param status_code: http request status code
239
- :type status_code: int
240
-
241
- :param elapsed: response time elapsed.
242
- :type status_code: int
243
- """
244
- self._telemetry_runtime_producer.record_sync_latency(self._metric_name, elapsed)
245
- if 200 <= status_code < 300:
246
- self._telemetry_runtime_producer.record_successful_sync(self._metric_name, get_current_epoch_time_ms())
247
- return
248
-
249
- self._telemetry_runtime_producer.record_sync_error(self._metric_name, status_code)
213
+ start = get_current_epoch_time_ms()
214
+ try:
215
+ response = requests.post(
216
+ _build_url(server, path, self._urls),
217
+ json=body,
218
+ params=query,
219
+ headers=self._get_headers(extra_headers, sdk_key),
220
+ timeout=self._timeout,
221
+ )
222
+ self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
223
+ return HttpResponse(response.status_code, response.text, response.headers)
224
+ except Exception as exc: # pylint: disable=broad-except
225
+ raise HttpClientException(_EXC_MSG.format(source='request')) from exc
250
226
 
251
227
  class HttpClientAsync(HttpClientBase):
252
228
  """HttpClientAsync wrapper."""
@@ -306,7 +282,7 @@ class HttpClientAsync(HttpClientBase):
306
282
  return HttpResponse(response.status, body, response.headers)
307
283
 
308
284
  except aiohttp.ClientError as exc: # pylint: disable=broad-except
309
- raise HttpClientException('aiohttp library is throwing exceptions') from exc
285
+ raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc
310
286
 
311
287
  async def post(self, server, path, apikey, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
312
288
  """
@@ -350,7 +326,7 @@ class HttpClientAsync(HttpClientBase):
350
326
  return HttpResponse(response.status, body, response.headers)
351
327
 
352
328
  except aiohttp.ClientError as exc: # pylint: disable=broad-except
353
- raise HttpClientException('aiohttp library is throwing exceptions') from exc
329
+ raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc
354
330
 
355
331
  async def _record_telemetry(self, status_code, elapsed):
356
332
  """
@@ -372,3 +348,191 @@ class HttpClientAsync(HttpClientBase):
372
348
  async def close_session(self):
373
349
  if not self._session.closed:
374
350
  await self._session.close()
351
+
352
+ class HttpClientKerberos(HttpClientBase):
353
+ """HttpClient wrapper."""
354
+
355
+ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, authentication_scheme=None, authentication_params=None):
356
+ """
357
+ Class constructor.
358
+
359
+ :param timeout: How many milliseconds to wait until the server responds.
360
+ :type timeout: int
361
+ :param sdk_url: Optional alternative sdk URL.
362
+ :type sdk_url: str
363
+ :param events_url: Optional alternative events URL.
364
+ :type events_url: str
365
+ :param auth_url: Optional alternative auth URL.
366
+ :type auth_url: str
367
+ :param telemetry_url: Optional alternative telemetry URL.
368
+ :type telemetry_url: str
369
+ :param authentication_scheme: Optional authentication scheme to use.
370
+ :type authentication_scheme: splitio.client.config.AuthenticateScheme
371
+ :param authentication_params: Optional authentication username and password to use.
372
+ :type authentication_params: [str, str]
373
+ """
374
+ _LOGGER.debug("Initializing httpclient for Kerberos auth")
375
+ self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
376
+ self._urls = _construct_urls(sdk_url, events_url, auth_url, telemetry_url)
377
+ self._authentication_scheme = authentication_scheme
378
+ self._authentication_params = authentication_params
379
+ self._lock = threading.RLock()
380
+ self._sessions = {'sdk': requests.Session(),
381
+ 'events': requests.Session(),
382
+ 'auth': requests.Session(),
383
+ 'telemetry': requests.Session()}
384
+ self._set_authentication()
385
+
386
+ def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments
387
+ """
388
+ Issue a get request.
389
+ :param server: Whether the request is for SDK server, Events server or Auth server.
390
+ :typee server: str
391
+ :param path: path to append to the host url.
392
+ :type path: str
393
+ :param sdk_key: sdk key.
394
+ :type sdk_key: str
395
+ :param query: Query string passed as dictionary.
396
+ :type query: dict
397
+ :param extra_headers: key/value pairs of possible extra headers.
398
+ :type extra_headers: dict
399
+
400
+ :return: Tuple of status_code & response text
401
+ :rtype: HttpResponse
402
+ """
403
+ with self._lock:
404
+ start = get_current_epoch_time_ms()
405
+ try:
406
+ return self._do_get(server, path, sdk_key, query, extra_headers, start)
407
+
408
+ except requests.exceptions.ProxyError as exc:
409
+ _LOGGER.debug("Proxy Exception caught, resetting the http session")
410
+ self._sessions[server].close()
411
+ self._sessions[server] = requests.Session()
412
+ self._set_authentication(server_name=server)
413
+ try:
414
+ return self._do_get(server, path, sdk_key, query, extra_headers, start)
415
+
416
+ except Exception as exc:
417
+ raise HttpClientException(_EXC_MSG.format(source='request')) from exc
418
+
419
+ except Exception as exc: # pylint: disable=broad-except
420
+ raise HttpClientException(_EXC_MSG.format(source='request')) from exc
421
+
422
+ def _do_get(self, server, path, sdk_key, query, extra_headers, start):
423
+ """
424
+ Issue a get request.
425
+ :param server: Whether the request is for SDK server, Events server or Auth server.
426
+ :typee server: str
427
+ :param path: path to append to the host url.
428
+ :type path: str
429
+ :param sdk_key: sdk key.
430
+ :type sdk_key: str
431
+ :param query: Query string passed as dictionary.
432
+ :type query: dict
433
+ :param extra_headers: key/value pairs of possible extra headers.
434
+ :type extra_headers: dict
435
+
436
+ :return: Tuple of status_code & response text
437
+ :rtype: HttpResponse
438
+ """
439
+ with self._sessions[server].get(
440
+ _build_url(server, path, self._urls),
441
+ headers=self._get_headers(extra_headers, sdk_key),
442
+ params=query,
443
+ timeout=self._timeout
444
+ ) as response:
445
+ self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
446
+ return HttpResponse(response.status_code, response.text, response.headers)
447
+
448
+ def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
449
+ """
450
+ Issue a POST request.
451
+
452
+ :param server: Whether the request is for SDK server or Events server.
453
+ :typee server: str
454
+ :param path: path to append to the host url.
455
+ :type path: str
456
+ :param sdk_key: sdk key.
457
+ :type sdk_key: str
458
+ :param body: body sent in the request.
459
+ :type body: str
460
+ :param query: Query string passed as dictionary.
461
+ :type query: dict
462
+ :param extra_headers: key/value pairs of possible extra headers.
463
+ :type extra_headers: dict
464
+
465
+ :return: Tuple of status_code & response text
466
+ :rtype: HttpResponse
467
+ """
468
+ with self._lock:
469
+ start = get_current_epoch_time_ms()
470
+ try:
471
+ return self._do_post(server, path, sdk_key, query, extra_headers, body, start)
472
+
473
+ except requests.exceptions.ProxyError as exc:
474
+ _LOGGER.debug("Proxy Exception caught, resetting the http session")
475
+ self._sessions[server].close()
476
+ self._sessions[server] = requests.Session()
477
+ self._set_authentication(server_name=server)
478
+ try:
479
+ return self._do_post(server, path, sdk_key, query, extra_headers, body, start)
480
+
481
+ except Exception as exc:
482
+ raise HttpClientException(_EXC_MSG.format(source='request')) from exc
483
+
484
+ except Exception as exc: # pylint: disable=broad-except
485
+ raise HttpClientException(_EXC_MSG.format(source='request')) from exc
486
+
487
+ def _do_post(self, server, path, sdk_key, query, extra_headers, body, start):
488
+ """
489
+ Issue a POST request.
490
+
491
+ :param server: Whether the request is for SDK server or Events server.
492
+ :typee server: str
493
+ :param path: path to append to the host url.
494
+ :type path: str
495
+ :param sdk_key: sdk key.
496
+ :type sdk_key: str
497
+ :param body: body sent in the request.
498
+ :type body: str
499
+ :param query: Query string passed as dictionary.
500
+ :type query: dict
501
+ :param extra_headers: key/value pairs of possible extra headers.
502
+ :type extra_headers: dict
503
+
504
+ :return: Tuple of status_code & response text
505
+ :rtype: HttpResponse
506
+ """
507
+ with self._sessions[server].post(
508
+ _build_url(server, path, self._urls),
509
+ params=query,
510
+ headers=self._get_headers(extra_headers, sdk_key),
511
+ json=body,
512
+ timeout=self._timeout,
513
+ ) as response:
514
+ self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
515
+ return HttpResponse(response.status_code, response.text, response.headers)
516
+
517
+ def _set_authentication(self, server_name=None):
518
+ """
519
+ Set the authentication for all self._sessions variables based on authentication scheme.
520
+
521
+ :param server: If set, will only add the auth for its session variable, otherwise will set all sessions.
522
+ :typee server: str
523
+ """
524
+ for server in ['sdk', 'events', 'auth', 'telemetry']:
525
+ if server_name is not None and server_name != server:
526
+ continue
527
+ if self._authentication_scheme == AuthenticateScheme.KERBEROS_SPNEGO:
528
+ _LOGGER.debug("Using Kerberos Spnego Authentication")
529
+ if self._authentication_params != [None, None]:
530
+ self._sessions[server].auth = HTTPKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1], mutual_authentication=OPTIONAL)
531
+ else:
532
+ self._sessions[server].auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
533
+ elif self._authentication_scheme == AuthenticateScheme.KERBEROS_PROXY:
534
+ _LOGGER.debug("Using Kerberos Proxy Authentication")
535
+ if self._authentication_params != [None, None]:
536
+ self._sessions[server].mount('https://', HTTPAdapterWithProxyKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1]))
537
+ else:
538
+ self._sessions[server].mount('https://', HTTPAdapterWithProxyKerberosAuth())
@@ -3,7 +3,7 @@ import logging
3
3
 
4
4
  from splitio.engine.evaluator import Evaluator, CONTROL, EvaluationDataFactory, AsyncEvaluationDataFactory
5
5
  from splitio.engine.splitters import Splitter
6
- from splitio.models.impressions import Impression, Label
6
+ from splitio.models.impressions import Impression, Label, ImpressionDecorated
7
7
  from splitio.models.events import Event, EventWrapper
8
8
  from splitio.models.telemetry import get_latency_bucket_index, MethodExceptionsAndLatencies
9
9
  from splitio.client import input_validator
@@ -22,7 +22,8 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
22
22
  'impression': {
23
23
  'label': Label.EXCEPTION,
24
24
  'change_number': None,
25
- }
25
+ },
26
+ 'impressions_disabled': False
26
27
  }
27
28
 
28
29
  _NON_READY_EVAL_RESULT = {
@@ -31,7 +32,8 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
31
32
  'impression': {
32
33
  'label': Label.NOT_READY,
33
34
  'change_number': None
34
- }
35
+ },
36
+ 'impressions_disabled': False
35
37
  }
36
38
 
37
39
  def __init__(self, factory, recorder, labels_enabled=True):
@@ -116,14 +118,15 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
116
118
 
117
119
  def _build_impression(self, key, bucketing, feature, result):
118
120
  """Build an impression based on evaluation data & it's result."""
119
- return Impression(
120
- matching_key=key,
121
+ return ImpressionDecorated(
122
+ Impression(matching_key=key,
121
123
  feature_name=feature,
122
124
  treatment=result['treatment'],
123
125
  label=result['impression']['label'] if self._labels_enabled else None,
124
126
  change_number=result['impression']['change_number'],
125
127
  bucketing_key=bucketing,
126
- time=utctime_ms())
128
+ time=utctime_ms()),
129
+ disabled=result['impressions_disabled'])
127
130
 
128
131
  def _build_impressions(self, key, bucketing, results):
129
132
  """Build an impression based on evaluation data & it's result."""
@@ -296,8 +299,8 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
296
299
  result = self._FAILED_EVAL_RESULT
297
300
 
298
301
  if result['impression']['label'] != Label.SPLIT_NOT_FOUND:
299
- impression = self._build_impression(key, bucketing, feature, result)
300
- self._record_stats([(impression, attributes)], start, method)
302
+ impression_decorated = self._build_impression(key, bucketing, feature, result)
303
+ self._record_stats([(impression_decorated, attributes)], start, method)
301
304
 
302
305
  return result['treatment'], result['configurations']
303
306
 
@@ -571,23 +574,23 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
571
574
  self._telemetry_evaluation_producer.record_exception(method)
572
575
  results = {n: self._FAILED_EVAL_RESULT for n in features}
573
576
 
574
- imp_attrs = [
577
+ imp_decorated_attrs = [
575
578
  (i, attributes) for i in self._build_impressions(key, bucketing, results)
576
- if i.label != Label.SPLIT_NOT_FOUND
579
+ if i.Impression.label != Label.SPLIT_NOT_FOUND
577
580
  ]
578
- self._record_stats(imp_attrs, start, method)
581
+ self._record_stats(imp_decorated_attrs, start, method)
579
582
 
580
583
  return {
581
584
  feature: (results[feature]['treatment'], results[feature]['configurations'])
582
585
  for feature in results
583
586
  }
584
587
 
585
- def _record_stats(self, impressions, start, operation):
588
+ def _record_stats(self, impressions_decorated, start, operation):
586
589
  """
587
590
  Record impressions.
588
591
 
589
- :param impressions: Generated impressions
590
- :type impressions: list[tuple[splitio.models.impression.Impression, dict]]
592
+ :param impressions_decorated: Generated impressions
593
+ :type impressions_decorated: list[tuple[splitio.models.impression.ImpressionDecorated, dict]]
591
594
 
592
595
  :param start: timestamp when get_treatment or get_treatments was called
593
596
  :type start: int
@@ -596,7 +599,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
596
599
  :type operation: str
597
600
  """
598
601
  end = get_current_epoch_time_ms()
599
- self._recorder.record_treatment_stats(impressions, get_latency_bucket_index(end - start),
602
+ self._recorder.record_treatment_stats(impressions_decorated, get_latency_bucket_index(end - start),
600
603
  operation, 'get_' + operation.value)
601
604
 
602
605
  def track(self, key, traffic_type, event_type, value=None, properties=None):
@@ -763,8 +766,8 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
763
766
  result = self._FAILED_EVAL_RESULT
764
767
 
765
768
  if result['impression']['label'] != Label.SPLIT_NOT_FOUND:
766
- impression = self._build_impression(key, bucketing, feature, result)
767
- await self._record_stats([(impression, attributes)], start, method)
769
+ impression_decorated = self._build_impression(key, bucketing, feature, result)
770
+ await self._record_stats([(impression_decorated, attributes)], start, method)
768
771
  return result['treatment'], result['configurations']
769
772
 
770
773
  async def get_treatments(self, key, feature_flag_names, attributes=None):
@@ -960,23 +963,23 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
960
963
  await self._telemetry_evaluation_producer.record_exception(method)
961
964
  results = {n: self._FAILED_EVAL_RESULT for n in features}
962
965
 
963
- imp_attrs = [
966
+ imp_decorated_attrs = [
964
967
  (i, attributes) for i in self._build_impressions(key, bucketing, results)
965
- if i.label != Label.SPLIT_NOT_FOUND
968
+ if i.Impression.label != Label.SPLIT_NOT_FOUND
966
969
  ]
967
- await self._record_stats(imp_attrs, start, method)
970
+ await self._record_stats(imp_decorated_attrs, start, method)
968
971
 
969
972
  return {
970
973
  feature: (res['treatment'], res['configurations'])
971
974
  for feature, res in results.items()
972
975
  }
973
976
 
974
- async def _record_stats(self, impressions, start, operation):
977
+ async def _record_stats(self, impressions_decorated, start, operation):
975
978
  """
976
979
  Record impressions for async calls
977
980
 
978
- :param impressions: Generated impressions
979
- :type impressions: list[tuple[splitio.models.impression.Impression, dict]]
981
+ :param impressions_decorated: Generated impressions decorated
982
+ :type impressions_decorated: list[tuple[splitio.models.impression.Impression, dict]]
980
983
 
981
984
  :param start: timestamp when get_treatment or get_treatments was called
982
985
  :type start: int
@@ -985,7 +988,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
985
988
  :type operation: str
986
989
  """
987
990
  end = get_current_epoch_time_ms()
988
- await self._recorder.record_treatment_stats(impressions, get_latency_bucket_index(end - start),
991
+ await self._recorder.record_treatment_stats(impressions_decorated, get_latency_bucket_index(end - start),
989
992
  operation, 'get_' + operation.value)
990
993
 
991
994
  async def track(self, key, traffic_type, event_type, value=None, properties=None):