rudder-sdk-python 2.1.4__tar.gz → 2.1.8__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.
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/PKG-INFO +37 -2
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/README.md +36 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/PKG-INFO +37 -2
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/SOURCES.txt +3 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/requires.txt +0 -1
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/__init__.py +1 -2
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/consumer.py +3 -3
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/request.py +6 -3
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_client.py +13 -7
- rudder_sdk_python-2.1.8/rudderstack/analytics/test/test_constants.py +4 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_consumer.py +9 -3
- rudder_sdk_python-2.1.8/rudderstack/analytics/test/test_public_api_contract.py +96 -0
- rudder_sdk_python-2.1.8/rudderstack/analytics/test/test_release_validation.py +62 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_request.py +13 -8
- rudder_sdk_python-2.1.8/rudderstack/analytics/test/test_request_options.py +136 -0
- rudder_sdk_python-2.1.8/rudderstack/analytics/version.py +1 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/setup.py +0 -1
- rudder_sdk_python-2.1.4/rudderstack/analytics/test/test_constants.py +0 -1
- rudder_sdk_python-2.1.4/rudderstack/analytics/version.py +0 -1
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/LICENSE.md +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/dependency_links.txt +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/not-zip-safe +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/top_level.txt +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/client.py +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/get_env.py +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/__init__.py +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_module.py +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_utils.py +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/utils.py +0 -0
- {rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rudder-sdk-python
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8
|
|
4
4
|
Summary: RudderStack is an open-source Segment alternative written in Go, built for the enterprise.
|
|
5
5
|
Home-page: https://github.com/rudderlabs/rudder-sdk-python
|
|
6
6
|
Author: RudderStack
|
|
@@ -25,7 +25,6 @@ Requires-Python: >=3.8.0
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE.md
|
|
27
27
|
Requires-Dist: requests<3.0,>=2.32.3
|
|
28
|
-
Requires-Dist: monotonic<2.0,>=1.6
|
|
29
28
|
Requires-Dist: backoff<3.0,>=2.2.1
|
|
30
29
|
Requires-Dist: python-dateutil<3.0,>=2.2
|
|
31
30
|
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|
|
@@ -34,8 +33,19 @@ Provides-Extra: test
|
|
|
34
33
|
Requires-Dist: mock==5.1.0; extra == "test"
|
|
35
34
|
Requires-Dist: flake8==7.1.1; extra == "test"
|
|
36
35
|
|
|
36
|
+
<p align="center">
|
|
37
|
+
<a href="https://rudderstack.com/">
|
|
38
|
+
<picture>
|
|
39
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rudderlabs/rudder-sdk-js/develop/assets/rs-logo-full-dark.png">
|
|
40
|
+
<img alt="RudderStack" width="512" src="https://raw.githubusercontent.com/rudderlabs/rudder-sdk-js/develop/assets/rs-logo-full-light.jpg">
|
|
41
|
+
</picture>
|
|
42
|
+
</a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
37
45
|
# RudderStack Python SDK
|
|
38
46
|
|
|
47
|
+
[](https://deepwiki.com/rudderlabs/rudder-sdk-python)
|
|
48
|
+
|
|
39
49
|
RudderStack’s Python SDK lets you track events from your Python application. Once enabled, the event requests hit the RudderStack servers. RudderStack then transforms and routes these events to your specified destination platforms.
|
|
40
50
|
|
|
41
51
|
More details on the Python SDK can be found in our [**documentation**](https://docs.rudderstack.com/stream-sources/rudderstack-sdk-integration-guides/rudderstack-python-sdk)
|
|
@@ -79,6 +89,31 @@ analytics.track('user_id', 'Simple Track Event', anonymous_id='anonymousId',
|
|
|
79
89
|
|
|
80
90
|
For more information on the supported calls, refer to the [**documentation**](https://docs.rudderstack.com/stream-sources/rudderstack-sdk-integration-guides/rudderstack-python-sdk#sending-events-from-rudderstack).
|
|
81
91
|
|
|
92
|
+
## Proxy configuration
|
|
93
|
+
|
|
94
|
+
Pass `proxies` when you create a `Client`. A Requests-style mapping is preferred:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from rudderstack.analytics.client import Client
|
|
98
|
+
|
|
99
|
+
client = Client(
|
|
100
|
+
'SOURCE_WRITE_KEY',
|
|
101
|
+
host='https://YOUR_DATA_PLANE_URL',
|
|
102
|
+
proxies={
|
|
103
|
+
'http': 'http://proxy.example:8080',
|
|
104
|
+
'https': 'http://proxy.example:8080',
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A legacy string such as `proxy.example:8080` is also supported. The SDK applies
|
|
110
|
+
the string to both HTTP and HTTPS requests. If the string has no scheme, the SDK
|
|
111
|
+
adds `http://`. An existing scheme is preserved.
|
|
112
|
+
|
|
113
|
+
Configured proxies are now used for event requests. Earlier versions ignored
|
|
114
|
+
this option. Check that any existing proxy configuration is reachable before
|
|
115
|
+
upgrading.
|
|
116
|
+
|
|
82
117
|
## About RudderStack
|
|
83
118
|
|
|
84
119
|
[**RudderStack**](https://rudderstack.com/) is a customer data platform for developers. Our tooling makes it easy to deploy pipelines that collect customer data from every app, website and SaaS platform, then activate it in your warehouse and business tools.
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://rudderstack.com/">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rudderlabs/rudder-sdk-js/develop/assets/rs-logo-full-dark.png">
|
|
5
|
+
<img alt="RudderStack" width="512" src="https://raw.githubusercontent.com/rudderlabs/rudder-sdk-js/develop/assets/rs-logo-full-light.jpg">
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
1
10
|
# RudderStack Python SDK
|
|
2
11
|
|
|
12
|
+
[](https://deepwiki.com/rudderlabs/rudder-sdk-python)
|
|
13
|
+
|
|
3
14
|
RudderStack’s Python SDK lets you track events from your Python application. Once enabled, the event requests hit the RudderStack servers. RudderStack then transforms and routes these events to your specified destination platforms.
|
|
4
15
|
|
|
5
16
|
More details on the Python SDK can be found in our [**documentation**](https://docs.rudderstack.com/stream-sources/rudderstack-sdk-integration-guides/rudderstack-python-sdk)
|
|
@@ -43,6 +54,31 @@ analytics.track('user_id', 'Simple Track Event', anonymous_id='anonymousId',
|
|
|
43
54
|
|
|
44
55
|
For more information on the supported calls, refer to the [**documentation**](https://docs.rudderstack.com/stream-sources/rudderstack-sdk-integration-guides/rudderstack-python-sdk#sending-events-from-rudderstack).
|
|
45
56
|
|
|
57
|
+
## Proxy configuration
|
|
58
|
+
|
|
59
|
+
Pass `proxies` when you create a `Client`. A Requests-style mapping is preferred:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from rudderstack.analytics.client import Client
|
|
63
|
+
|
|
64
|
+
client = Client(
|
|
65
|
+
'SOURCE_WRITE_KEY',
|
|
66
|
+
host='https://YOUR_DATA_PLANE_URL',
|
|
67
|
+
proxies={
|
|
68
|
+
'http': 'http://proxy.example:8080',
|
|
69
|
+
'https': 'http://proxy.example:8080',
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A legacy string such as `proxy.example:8080` is also supported. The SDK applies
|
|
75
|
+
the string to both HTTP and HTTPS requests. If the string has no scheme, the SDK
|
|
76
|
+
adds `http://`. An existing scheme is preserved.
|
|
77
|
+
|
|
78
|
+
Configured proxies are now used for event requests. Earlier versions ignored
|
|
79
|
+
this option. Check that any existing proxy configuration is reachable before
|
|
80
|
+
upgrading.
|
|
81
|
+
|
|
46
82
|
## About RudderStack
|
|
47
83
|
|
|
48
84
|
[**RudderStack**](https://rudderstack.com/) is a customer data platform for developers. Our tooling makes it easy to deploy pipelines that collect customer data from every app, website and SaaS platform, then activate it in your warehouse and business tools.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rudder-sdk-python
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8
|
|
4
4
|
Summary: RudderStack is an open-source Segment alternative written in Go, built for the enterprise.
|
|
5
5
|
Home-page: https://github.com/rudderlabs/rudder-sdk-python
|
|
6
6
|
Author: RudderStack
|
|
@@ -25,7 +25,6 @@ Requires-Python: >=3.8.0
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE.md
|
|
27
27
|
Requires-Dist: requests<3.0,>=2.32.3
|
|
28
|
-
Requires-Dist: monotonic<2.0,>=1.6
|
|
29
28
|
Requires-Dist: backoff<3.0,>=2.2.1
|
|
30
29
|
Requires-Dist: python-dateutil<3.0,>=2.2
|
|
31
30
|
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|
|
@@ -34,8 +33,19 @@ Provides-Extra: test
|
|
|
34
33
|
Requires-Dist: mock==5.1.0; extra == "test"
|
|
35
34
|
Requires-Dist: flake8==7.1.1; extra == "test"
|
|
36
35
|
|
|
36
|
+
<p align="center">
|
|
37
|
+
<a href="https://rudderstack.com/">
|
|
38
|
+
<picture>
|
|
39
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rudderlabs/rudder-sdk-js/develop/assets/rs-logo-full-dark.png">
|
|
40
|
+
<img alt="RudderStack" width="512" src="https://raw.githubusercontent.com/rudderlabs/rudder-sdk-js/develop/assets/rs-logo-full-light.jpg">
|
|
41
|
+
</picture>
|
|
42
|
+
</a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
37
45
|
# RudderStack Python SDK
|
|
38
46
|
|
|
47
|
+
[](https://deepwiki.com/rudderlabs/rudder-sdk-python)
|
|
48
|
+
|
|
39
49
|
RudderStack’s Python SDK lets you track events from your Python application. Once enabled, the event requests hit the RudderStack servers. RudderStack then transforms and routes these events to your specified destination platforms.
|
|
40
50
|
|
|
41
51
|
More details on the Python SDK can be found in our [**documentation**](https://docs.rudderstack.com/stream-sources/rudderstack-sdk-integration-guides/rudderstack-python-sdk)
|
|
@@ -79,6 +89,31 @@ analytics.track('user_id', 'Simple Track Event', anonymous_id='anonymousId',
|
|
|
79
89
|
|
|
80
90
|
For more information on the supported calls, refer to the [**documentation**](https://docs.rudderstack.com/stream-sources/rudderstack-sdk-integration-guides/rudderstack-python-sdk#sending-events-from-rudderstack).
|
|
81
91
|
|
|
92
|
+
## Proxy configuration
|
|
93
|
+
|
|
94
|
+
Pass `proxies` when you create a `Client`. A Requests-style mapping is preferred:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from rudderstack.analytics.client import Client
|
|
98
|
+
|
|
99
|
+
client = Client(
|
|
100
|
+
'SOURCE_WRITE_KEY',
|
|
101
|
+
host='https://YOUR_DATA_PLANE_URL',
|
|
102
|
+
proxies={
|
|
103
|
+
'http': 'http://proxy.example:8080',
|
|
104
|
+
'https': 'http://proxy.example:8080',
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A legacy string such as `proxy.example:8080` is also supported. The SDK applies
|
|
110
|
+
the string to both HTTP and HTTPS requests. If the string has no scheme, the SDK
|
|
111
|
+
adds `http://`. An existing scheme is preserved.
|
|
112
|
+
|
|
113
|
+
Configured proxies are now used for event requests. Earlier versions ignored
|
|
114
|
+
this option. Check that any existing proxy configuration is reachable before
|
|
115
|
+
upgrading.
|
|
116
|
+
|
|
82
117
|
## About RudderStack
|
|
83
118
|
|
|
84
119
|
[**RudderStack**](https://rudderstack.com/) is a customer data platform for developers. Our tooling makes it easy to deploy pipelines that collect customer data from every app, website and SaaS platform, then activate it in your warehouse and business tools.
|
|
@@ -20,5 +20,8 @@ rudderstack/analytics/test/test_client.py
|
|
|
20
20
|
rudderstack/analytics/test/test_constants.py
|
|
21
21
|
rudderstack/analytics/test/test_consumer.py
|
|
22
22
|
rudderstack/analytics/test/test_module.py
|
|
23
|
+
rudderstack/analytics/test/test_public_api_contract.py
|
|
24
|
+
rudderstack/analytics/test/test_release_validation.py
|
|
23
25
|
rudderstack/analytics/test/test_request.py
|
|
26
|
+
rudderstack/analytics/test/test_request_options.py
|
|
24
27
|
rudderstack/analytics/test/test_utils.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import time
|
|
2
3
|
from threading import Thread
|
|
3
|
-
import monotonic
|
|
4
4
|
import backoff
|
|
5
5
|
import json
|
|
6
6
|
|
|
@@ -79,11 +79,11 @@ class Consumer(Thread):
|
|
|
79
79
|
queue = self.queue
|
|
80
80
|
items = []
|
|
81
81
|
|
|
82
|
-
start_time =
|
|
82
|
+
start_time = time.monotonic()
|
|
83
83
|
total_size = 0
|
|
84
84
|
|
|
85
85
|
while len(items) < self.upload_size:
|
|
86
|
-
elapsed =
|
|
86
|
+
elapsed = time.monotonic() - start_time
|
|
87
87
|
if elapsed >= self.upload_interval:
|
|
88
88
|
break
|
|
89
89
|
try:
|
|
@@ -34,14 +34,17 @@ def post(write_key, host=None, gzip=True, timeout=15, proxies=None, **kwargs):
|
|
|
34
34
|
"data": data,
|
|
35
35
|
"auth": auth,
|
|
36
36
|
"headers": headers,
|
|
37
|
-
"timeout":
|
|
37
|
+
"timeout": timeout,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
if proxies:
|
|
41
|
+
# Older SDK versions accepted a single proxy string.
|
|
42
|
+
if isinstance(proxies, str):
|
|
43
|
+
proxy_url = proxies if '://' in proxies else 'http://' + proxies
|
|
44
|
+
proxies = {'http': proxy_url, 'https': proxy_url}
|
|
41
45
|
kwargs['proxies'] = proxies
|
|
42
46
|
|
|
43
|
-
res = _session.post(url,
|
|
44
|
-
headers=headers, timeout=timeout)
|
|
47
|
+
res = _session.post(url, **kwargs)
|
|
45
48
|
|
|
46
49
|
if res.status_code == 200:
|
|
47
50
|
log.debug('data uploaded successfully')
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_client.py
RENAMED
|
@@ -325,10 +325,14 @@ class TestClient(unittest.TestCase):
|
|
|
325
325
|
|
|
326
326
|
def test_user_defined_upload_size(self):
|
|
327
327
|
client = Client(write_key = TEST_WRITE_KEY, on_error=self.fail,
|
|
328
|
-
upload_size=10, upload_interval=
|
|
329
|
-
|
|
328
|
+
upload_size=10, upload_interval=0.01)
|
|
329
|
+
consumer = client.consumers[0]
|
|
330
|
+
consumer.pause()
|
|
331
|
+
consumer.join()
|
|
332
|
+
|
|
333
|
+
batch_sizes = []
|
|
330
334
|
def mock_post_fn(*args, **kwargs):
|
|
331
|
-
|
|
335
|
+
batch_sizes.append(len(kwargs['batch']))
|
|
332
336
|
|
|
333
337
|
# the post function should be called 2 times, with a batch size of 10
|
|
334
338
|
# each time.
|
|
@@ -336,8 +340,10 @@ class TestClient(unittest.TestCase):
|
|
|
336
340
|
as mock_post:
|
|
337
341
|
for _ in range(20):
|
|
338
342
|
client.identify('userId', {'trait': 'value'})
|
|
339
|
-
|
|
343
|
+
consumer.upload()
|
|
344
|
+
consumer.upload()
|
|
340
345
|
self.assertEqual(mock_post.call_count, 2)
|
|
346
|
+
self.assertEqual(batch_sizes, [10, 10])
|
|
341
347
|
|
|
342
348
|
def test_user_defined_timeout(self):
|
|
343
349
|
client = Client(TEST_WRITE_KEY, timeout=10)
|
|
@@ -350,6 +356,6 @@ class TestClient(unittest.TestCase):
|
|
|
350
356
|
self.assertEqual(consumer.timeout, 15)
|
|
351
357
|
|
|
352
358
|
def test_proxies(self):
|
|
353
|
-
client = Client(TEST_WRITE_KEY, proxies=TEST_PROXY)
|
|
354
|
-
|
|
355
|
-
self.
|
|
359
|
+
client = Client(TEST_WRITE_KEY, proxies=TEST_PROXY, send=False)
|
|
360
|
+
|
|
361
|
+
self.assertEqual(client.consumers[0].proxies, TEST_PROXY)
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_consumer.py
RENAMED
|
@@ -208,14 +208,17 @@ class TestConsumer(unittest.TestCase):
|
|
|
208
208
|
|
|
209
209
|
with mock.patch('rudderstack.analytics.request._session.post',
|
|
210
210
|
side_effect=mock_post_fn) as mock_post:
|
|
211
|
-
consumer.start()
|
|
212
211
|
for _ in range(0, n_msgs + 2):
|
|
213
212
|
q.put(track)
|
|
213
|
+
consumer.start()
|
|
214
214
|
q.join()
|
|
215
|
+
consumer.pause()
|
|
216
|
+
consumer.join()
|
|
215
217
|
self.assertEqual(mock_post.call_count, 2)
|
|
216
218
|
|
|
217
|
-
@
|
|
218
|
-
def test_proxies(
|
|
219
|
+
@mock.patch('rudderstack.analytics.request._session.post')
|
|
220
|
+
def test_proxies(self, session_post):
|
|
221
|
+
session_post.return_value = mock.Mock(status_code=200)
|
|
219
222
|
consumer = Consumer(None, host=TEST_DATA_PLANE_URL,
|
|
220
223
|
write_key=TEST_WRITE_KEY, proxies=TEST_PROXY)
|
|
221
224
|
track = {
|
|
@@ -224,3 +227,6 @@ class TestConsumer(unittest.TestCase):
|
|
|
224
227
|
'userId': 'userId'
|
|
225
228
|
}
|
|
226
229
|
consumer.request([track])
|
|
230
|
+
|
|
231
|
+
_, kwargs = session_post.call_args
|
|
232
|
+
self.assertEqual(kwargs['proxies'], TEST_PROXY)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Compatibility checks for the existing module and Client APIs."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from unittest import mock
|
|
5
|
+
|
|
6
|
+
import rudderstack.analytics as analytics
|
|
7
|
+
from rudderstack.analytics.client import Client
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
EVENT_CALLS = (
|
|
11
|
+
('track', ('user-id', 'test event')),
|
|
12
|
+
('identify', ('user-id',)),
|
|
13
|
+
('group', ('user-id', 'group-id')),
|
|
14
|
+
('alias', ('previous-id', 'user-id')),
|
|
15
|
+
('page', ('user-id',)),
|
|
16
|
+
('screen', ('user-id',)),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestPublicApiContract(unittest.TestCase):
|
|
21
|
+
def test_module_discards_client_results(self):
|
|
22
|
+
client = mock.Mock()
|
|
23
|
+
with mock.patch.object(analytics, 'default_client', client):
|
|
24
|
+
for method, args in EVENT_CALLS:
|
|
25
|
+
with self.subTest(method=method):
|
|
26
|
+
getattr(client, method).return_value = mock.sentinel.result
|
|
27
|
+
self.assertIsNone(getattr(analytics, method)(
|
|
28
|
+
*args, message_id='message-id'))
|
|
29
|
+
getattr(client, method).assert_called_once_with(
|
|
30
|
+
*args, message_id='message-id')
|
|
31
|
+
for method in ('flush', 'join', 'shutdown'):
|
|
32
|
+
with self.subTest(method=method):
|
|
33
|
+
getattr(client, method).return_value = mock.sentinel.result
|
|
34
|
+
self.assertIsNone(getattr(analytics, method)())
|
|
35
|
+
|
|
36
|
+
def test_module_returns_none_when_queue_accepts_or_rejects_event(self):
|
|
37
|
+
for method, args in EVENT_CALLS:
|
|
38
|
+
with self.subTest(method=method):
|
|
39
|
+
client = Client('test-key', send=False, max_queue_size=1)
|
|
40
|
+
# Enable enqueueing without starting a background consumer.
|
|
41
|
+
client.send = True
|
|
42
|
+
with mock.patch.object(analytics, 'default_client', client):
|
|
43
|
+
self.assertIsNone(getattr(analytics, method)(*args))
|
|
44
|
+
self.assertEqual(client.queue.qsize(), 1)
|
|
45
|
+
self.assertIsNone(getattr(analytics, method)(*args))
|
|
46
|
+
self.assertEqual(client.queue.qsize(), 1)
|
|
47
|
+
|
|
48
|
+
def test_client_returns_success_and_message(self):
|
|
49
|
+
for method, args in EVENT_CALLS:
|
|
50
|
+
with self.subTest(method=method):
|
|
51
|
+
client = Client('test-key', send=False)
|
|
52
|
+
result = getattr(client, method)(
|
|
53
|
+
*args, message_id='message-id')
|
|
54
|
+
self.assertIsInstance(result, tuple)
|
|
55
|
+
self.assertEqual(len(result), 2)
|
|
56
|
+
success, message = result
|
|
57
|
+
self.assertIs(success, True)
|
|
58
|
+
self.assertEqual(message['type'], method)
|
|
59
|
+
self.assertEqual(message['messageId'], 'message-id')
|
|
60
|
+
|
|
61
|
+
def test_client_returns_false_and_message_when_queue_is_full(self):
|
|
62
|
+
for method, args in EVENT_CALLS:
|
|
63
|
+
with self.subTest(method=method):
|
|
64
|
+
client = Client('test-key', send=False, max_queue_size=1)
|
|
65
|
+
client.send = True
|
|
66
|
+
success, message = getattr(client, method)(*args)
|
|
67
|
+
self.assertIs(success, True)
|
|
68
|
+
self.assertEqual(client.queue.get_nowait(), message)
|
|
69
|
+
client.queue.task_done()
|
|
70
|
+
client.queue.put_nowait(message)
|
|
71
|
+
result = getattr(client, method)(*args)
|
|
72
|
+
self.assertIsInstance(result, tuple)
|
|
73
|
+
self.assertEqual(len(result), 2)
|
|
74
|
+
success, message = result
|
|
75
|
+
self.assertIs(success, False)
|
|
76
|
+
self.assertEqual(message['type'], method)
|
|
77
|
+
|
|
78
|
+
def test_module_preserves_validation_errors(self):
|
|
79
|
+
client = Client('test-key', send=False)
|
|
80
|
+
with mock.patch.object(analytics, 'default_client', client):
|
|
81
|
+
with self.assertRaises(AssertionError):
|
|
82
|
+
analytics.track('user-id', event=123)
|
|
83
|
+
|
|
84
|
+
def test_client_lifecycle_returns_none(self):
|
|
85
|
+
client = Client('test-key', send=False)
|
|
86
|
+
for method in ('flush', 'join', 'shutdown'):
|
|
87
|
+
with self.subTest(method=method):
|
|
88
|
+
self.assertIsNone(getattr(client, method)())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestShutdownDelegation(unittest.TestCase):
|
|
92
|
+
def test_module_calls_client_shutdown_once(self):
|
|
93
|
+
client = mock.Mock()
|
|
94
|
+
with mock.patch.object(analytics, 'default_client', client):
|
|
95
|
+
self.assertIsNone(analytics.shutdown())
|
|
96
|
+
self.assertEqual(client.mock_calls, [mock.call.shutdown()])
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import runpy
|
|
3
|
+
import subprocess
|
|
4
|
+
import textwrap
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
REPOSITORY_ROOT = Path(__file__).resolve().parents[3]
|
|
11
|
+
PUBLISH_WORKFLOW = REPOSITORY_ROOT / '.github' / 'workflows' / 'publish-pypi.yml'
|
|
12
|
+
VERSION_FILE = REPOSITORY_ROOT / 'rudderstack' / 'analytics' / 'version.py'
|
|
13
|
+
VERSION = runpy.run_path(str(VERSION_FILE))['VERSION']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def release_validation_script():
|
|
17
|
+
workflow = PUBLISH_WORKFLOW.read_text(encoding='utf-8')
|
|
18
|
+
validation_step = workflow.split(
|
|
19
|
+
' - name: Validate release tag and package version\n', 1
|
|
20
|
+
)[1].split('\n - name:', 1)[0]
|
|
21
|
+
script = validation_step.split(' run: |\n', 1)[1]
|
|
22
|
+
return textwrap.dedent(script)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_validation(release_tag, check_syntax=False):
|
|
26
|
+
command = ['bash', '-n'] if check_syntax else ['bash']
|
|
27
|
+
return subprocess.run(
|
|
28
|
+
command,
|
|
29
|
+
check=False,
|
|
30
|
+
capture_output=True,
|
|
31
|
+
cwd=REPOSITORY_ROOT,
|
|
32
|
+
env={**os.environ, 'RELEASE_TAG': release_tag},
|
|
33
|
+
input=release_validation_script(),
|
|
34
|
+
text=True,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_release_validation_script_has_valid_shell_syntax():
|
|
39
|
+
result = run_validation('v{}'.format(VERSION), check_syntax=True)
|
|
40
|
+
|
|
41
|
+
assert result.returncode == 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_current_package_version_matches_release_tag():
|
|
45
|
+
result = run_validation('v{}'.format(VERSION))
|
|
46
|
+
|
|
47
|
+
assert result.returncode == 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize('release_tag', ['2.1.7', 'v2.1', 'release-v2.1.7'])
|
|
51
|
+
def test_invalid_release_tag_is_rejected(release_tag):
|
|
52
|
+
result = run_validation(release_tag)
|
|
53
|
+
|
|
54
|
+
assert result.returncode == 1
|
|
55
|
+
assert 'Invalid release tag' in result.stderr
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_release_tag_must_match_package_version():
|
|
59
|
+
result = run_validation('v999.0.0')
|
|
60
|
+
|
|
61
|
+
assert result.returncode == 1
|
|
62
|
+
assert 'does not match package version' in result.stderr
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_request.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from datetime import datetime, date
|
|
2
2
|
import unittest
|
|
3
|
+
from unittest import mock
|
|
3
4
|
import json
|
|
4
5
|
import requests
|
|
5
6
|
|
|
@@ -67,11 +68,15 @@ class TestRequests(unittest.TestCase):
|
|
|
67
68
|
data = json.dumps(body, cls=DatetimeSerializer)
|
|
68
69
|
self.assertTrue(len(data) > len(_gzip_json(data = data)))
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
@mock.patch('rudderstack.analytics.request._session.post')
|
|
72
|
+
def test_passes_proxy_and_timeout_to_session(self, session_post):
|
|
73
|
+
response = mock.Mock(status_code=200)
|
|
74
|
+
session_post.return_value = response
|
|
75
|
+
|
|
76
|
+
self.assertIs(
|
|
77
|
+
post(TEST_WRITE_KEY, host=TEST_DATA_PLANE_URL, proxies=TEST_PROXY,
|
|
78
|
+
timeout=7.5, batch=[]),
|
|
79
|
+
response)
|
|
80
|
+
_, kwargs = session_post.call_args
|
|
81
|
+
self.assertEqual(kwargs['proxies'], TEST_PROXY)
|
|
82
|
+
self.assertEqual(kwargs['timeout'], 7.5)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Request option tests without credentials or external network access."""
|
|
2
|
+
|
|
3
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
4
|
+
import gzip
|
|
5
|
+
import json
|
|
6
|
+
import socket
|
|
7
|
+
from threading import Thread
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest import mock
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
from rudderstack.analytics.client import Client
|
|
14
|
+
from rudderstack.analytics.consumer import Consumer
|
|
15
|
+
from rudderstack.analytics.request import post
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestRequestOptions(unittest.TestCase):
|
|
19
|
+
def setUp(self):
|
|
20
|
+
patcher = mock.patch('rudderstack.analytics.request._session.post')
|
|
21
|
+
self.session_post = patcher.start()
|
|
22
|
+
self.addCleanup(patcher.stop)
|
|
23
|
+
self.session_post.return_value = mock.Mock(status_code=200)
|
|
24
|
+
|
|
25
|
+
def test_legacy_proxy_strings(self):
|
|
26
|
+
for value, expected in (
|
|
27
|
+
('proxy.example:8080', 'http://proxy.example:8080'),
|
|
28
|
+
('http://proxy.example:8080', 'http://proxy.example:8080'),
|
|
29
|
+
('https://proxy.example:8080', 'https://proxy.example:8080'),
|
|
30
|
+
('socks5://proxy.example:1080', 'socks5://proxy.example:1080'),
|
|
31
|
+
):
|
|
32
|
+
with self.subTest(proxy=value):
|
|
33
|
+
post('test-key', proxies=value, batch=[])
|
|
34
|
+
self.assertEqual(self.session_post.call_args.kwargs['proxies'],
|
|
35
|
+
{'http': expected, 'https': expected})
|
|
36
|
+
|
|
37
|
+
def test_proxy_mapping_is_not_modified(self):
|
|
38
|
+
proxies = {'http': 'http://proxy.example:8080',
|
|
39
|
+
'https': 'http://secure-proxy.example:8080'}
|
|
40
|
+
original = proxies.copy()
|
|
41
|
+
post('test-key', proxies=proxies, batch=[])
|
|
42
|
+
self.assertIs(self.session_post.call_args.kwargs['proxies'], proxies)
|
|
43
|
+
self.assertEqual(proxies, original)
|
|
44
|
+
|
|
45
|
+
def test_empty_proxy_configuration_is_omitted(self):
|
|
46
|
+
for proxies in (None, '', {}):
|
|
47
|
+
with self.subTest(proxies=proxies):
|
|
48
|
+
post('test-key', proxies=proxies, batch=[])
|
|
49
|
+
self.assertNotIn('proxies', self.session_post.call_args.kwargs)
|
|
50
|
+
|
|
51
|
+
def test_default_and_custom_timeouts(self):
|
|
52
|
+
post('test-key', batch=[])
|
|
53
|
+
self.assertEqual(self.session_post.call_args.kwargs['timeout'], 15)
|
|
54
|
+
post('test-key', timeout=7.5, batch=[])
|
|
55
|
+
self.assertEqual(self.session_post.call_args.kwargs['timeout'], 7.5)
|
|
56
|
+
|
|
57
|
+
def test_sync_client_forwards_legacy_proxy_and_timeout(self):
|
|
58
|
+
client = Client('test-key', sync_mode=True,
|
|
59
|
+
proxies='proxy.example:8080', timeout=7.5)
|
|
60
|
+
success, message = client.track('user-id', 'test event')
|
|
61
|
+
self.assertIs(success, True)
|
|
62
|
+
self.assertEqual(message['type'], 'track')
|
|
63
|
+
self.assertEqual(self.session_post.call_args.kwargs['proxies'],
|
|
64
|
+
{'http': 'http://proxy.example:8080',
|
|
65
|
+
'https': 'http://proxy.example:8080'})
|
|
66
|
+
self.assertEqual(self.session_post.call_args.kwargs['timeout'], 7.5)
|
|
67
|
+
|
|
68
|
+
def test_consumer_forwards_legacy_proxy_and_timeout(self):
|
|
69
|
+
consumer = Consumer(None, 'test-key', proxies='proxy.example:8080',
|
|
70
|
+
timeout=7.5, retries=0)
|
|
71
|
+
consumer.request([{'type': 'track', 'event': 'test event'}])
|
|
72
|
+
self.assertEqual(self.session_post.call_args.kwargs['proxies'],
|
|
73
|
+
{'http': 'http://proxy.example:8080',
|
|
74
|
+
'https': 'http://proxy.example:8080'})
|
|
75
|
+
self.assertEqual(self.session_post.call_args.kwargs['timeout'], 7.5)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestProxyTransport(unittest.TestCase):
|
|
79
|
+
def test_real_requests_routes_through_proxy(self):
|
|
80
|
+
received = []
|
|
81
|
+
getaddrinfo = socket.getaddrinfo
|
|
82
|
+
|
|
83
|
+
def local_only(host, *args, **kwargs):
|
|
84
|
+
self.assertEqual(host, '127.0.0.1',
|
|
85
|
+
'Unexpected external connection')
|
|
86
|
+
return getaddrinfo(host, *args, **kwargs)
|
|
87
|
+
|
|
88
|
+
patcher = mock.patch('socket.getaddrinfo', side_effect=local_only)
|
|
89
|
+
patcher.start()
|
|
90
|
+
self.addCleanup(patcher.stop)
|
|
91
|
+
|
|
92
|
+
class ProxyHandler(BaseHTTPRequestHandler):
|
|
93
|
+
def do_POST(self):
|
|
94
|
+
body = self.rfile.read(int(self.headers['Content-Length']))
|
|
95
|
+
if self.headers.get('Content-Encoding') == 'gzip':
|
|
96
|
+
body = gzip.decompress(body)
|
|
97
|
+
received.append((self.path, json.loads(body)))
|
|
98
|
+
self.send_response(200)
|
|
99
|
+
self.end_headers()
|
|
100
|
+
self.wfile.write(b'{}')
|
|
101
|
+
|
|
102
|
+
def log_message(self, *args):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
server = HTTPServer(('127.0.0.1', 0), ProxyHandler)
|
|
106
|
+
thread = Thread(target=server.serve_forever,
|
|
107
|
+
kwargs={'poll_interval': 0.01}, daemon=True)
|
|
108
|
+
thread.start()
|
|
109
|
+
try:
|
|
110
|
+
address = '127.0.0.1:{}'.format(server.server_port)
|
|
111
|
+
with requests.Session() as session:
|
|
112
|
+
session.trust_env = False
|
|
113
|
+
with mock.patch('rudderstack.analytics.request._session',
|
|
114
|
+
session):
|
|
115
|
+
for proxies in (address, 'http://' + address,
|
|
116
|
+
{'http': 'http://' + address}):
|
|
117
|
+
for compressed in (False, True):
|
|
118
|
+
with self.subTest(proxies=proxies,
|
|
119
|
+
gzip=compressed):
|
|
120
|
+
response = post(
|
|
121
|
+
'test-key', host='http://data.example',
|
|
122
|
+
proxies=proxies, timeout=2,
|
|
123
|
+
gzip=compressed, batch=[])
|
|
124
|
+
self.assertEqual(response.status_code, 200)
|
|
125
|
+
path, payload = received[-1]
|
|
126
|
+
self.assertEqual(
|
|
127
|
+
path, 'http://data.example/v1/batch')
|
|
128
|
+
self.assertEqual(payload['batch'], [])
|
|
129
|
+
self.assertIn('sentAt', payload)
|
|
130
|
+
self.assertNotIn('proxies', payload)
|
|
131
|
+
self.assertNotIn('timeout', payload)
|
|
132
|
+
self.assertEqual(len(received), 6)
|
|
133
|
+
finally:
|
|
134
|
+
server.shutdown()
|
|
135
|
+
thread.join(timeout=2)
|
|
136
|
+
server.server_close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = '2.1.8' # x-release-please-version
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
TEST_PROXY = '203.243.63.16:80'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = '2.1.4'
|
|
File without changes
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudder_sdk_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_module.py
RENAMED
|
File without changes
|
{rudder_sdk_python-2.1.4 → rudder_sdk_python-2.1.8}/rudderstack/analytics/test/test_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|