dnaStreaming 2.0.2__tar.gz → 2.1.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.
- dnastreaming-2.1.0/LICENSE +21 -0
- dnastreaming-2.1.0/PKG-INFO +255 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/README.rst +30 -37
- dnastreaming-2.1.0/dnaStreaming/config.py +109 -0
- dnastreaming-2.1.0/dnaStreaming/customer_config.json +5 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/demo/show_stream.py +6 -1
- dnastreaming-2.1.0/dnaStreaming/listener.py +114 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/services/credentials_service.py +1 -1
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/test/test_config.py +0 -50
- dnastreaming-2.1.0/dnaStreaming.egg-info/PKG-INFO +255 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming.egg-info/SOURCES.txt +1 -0
- dnastreaming-2.1.0/dnaStreaming.egg-info/requires.txt +8 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/setup.py +11 -8
- dnaStreaming-2.0.2/PKG-INFO +0 -254
- dnaStreaming-2.0.2/dnaStreaming/config.py +0 -202
- dnaStreaming-2.0.2/dnaStreaming/customer_config.json +0 -8
- dnaStreaming-2.0.2/dnaStreaming/listener.py +0 -125
- dnaStreaming-2.0.2/dnaStreaming.egg-info/PKG-INFO +0 -254
- dnaStreaming-2.0.2/dnaStreaming.egg-info/requires.txt +0 -8
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/MANIFEST.in +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/__init__.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/demo/__init__.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/demo/pubsub_stream.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/demo/show_stream_async.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/services/__init__.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/services/authentication_service.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/services/pubsub_service.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming/test/__init__.py +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming.egg-info/dependency_links.txt +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/dnaStreaming.egg-info/top_level.txt +0 -0
- {dnaStreaming-2.0.2 → dnastreaming-2.1.0}/setup.cfg +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Dow Jones Corporation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dnaStreaming
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Dow Jones DNA Streaming Project
|
|
5
|
+
Home-page: https://github.com/dowjones/dj-dna-streams-python
|
|
6
|
+
Download-URL: https://github.com/dowjones/dj-dna-streams-python/archive/release-2.1.0.tar.gz
|
|
7
|
+
Author: Zachary Kagan
|
|
8
|
+
Author-email: zachary.kagan@dowjones.com
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: DOWJONES,FACTIVA,STREAMS
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Description-Content-Type: text/x-rst
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: googleapis-common-protos>=1.56.4
|
|
18
|
+
Requires-Dist: google-auth>=2.11.0
|
|
19
|
+
Requires-Dist: google-cloud-pubsub==2.13.6
|
|
20
|
+
Requires-Dist: google-cloud-core==2.3.2
|
|
21
|
+
Requires-Dist: google-api-core==2.10.1
|
|
22
|
+
Requires-Dist: mock>=3.0.5
|
|
23
|
+
Requires-Dist: oauth2client>=4.1.3
|
|
24
|
+
Requires-Dist: requests>=2.28.0
|
|
25
|
+
|
|
26
|
+
How To Use
|
|
27
|
+
----------
|
|
28
|
+
|
|
29
|
+
This a Python3 library is for Dow Jones customers consuming data from a Dow Jones Factiva Stream.
|
|
30
|
+
|
|
31
|
+
To use this we recommend 'pip installing' this by making the following addition to your requirements.txt:
|
|
32
|
+
|
|
33
|
+
.. code-block::
|
|
34
|
+
|
|
35
|
+
# To fetch latest version from PyPi
|
|
36
|
+
dnaStreaming
|
|
37
|
+
|
|
38
|
+
# to fetch latest version from GitHub
|
|
39
|
+
git+https://github.com/dowjones/dj-dna-streams-python#egg=dnaStreaming
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Auth
|
|
43
|
+
-----------
|
|
44
|
+
|
|
45
|
+
There is currently one way to authenticate, which is by using **your user key**.
|
|
46
|
+
|
|
47
|
+
Configuring
|
|
48
|
+
___________
|
|
49
|
+
|
|
50
|
+
To run this code, you need to provide credentials from one of the authentication methods and your subscriptions. There are 3 ways to do this: you can set environment variables, use a configuration file or pass an argument to a constructor.
|
|
51
|
+
|
|
52
|
+
1. Set environment variables.
|
|
53
|
+
###################################################################
|
|
54
|
+
|
|
55
|
+
To set your service account credentials, set the `USER_KEY` environment variable:
|
|
56
|
+
|
|
57
|
+
.. code-block::
|
|
58
|
+
|
|
59
|
+
export USER_KEY="1234567890-098765432"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
To set your subscription ID, simply set an environment variable named 'SUBSCRIPTION_ID' like so
|
|
63
|
+
|
|
64
|
+
.. code-block::
|
|
65
|
+
|
|
66
|
+
export SUBSCRIPTION_ID="ABC1234567889"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
The code above is the command line expression for setting this environment variable on Mac OSX. Other operating systems might have a slightly different techniques for setting environment variables on the command line.
|
|
70
|
+
|
|
71
|
+
2. Using the configuration file.
|
|
72
|
+
###################################################################
|
|
73
|
+
|
|
74
|
+
In this codebase you will find a file named 'customer_config.json'. You are not required to use this file. If you prefer to use this option, fill the JSON object within by adding your user key and your subscription ID. Remember that this is a JSON file so follow basic JSON formatting and syntax conventions.
|
|
75
|
+
|
|
76
|
+
> The listener will search for the `customer_config.json` file inside your `$HOME` directory by default.
|
|
77
|
+
|
|
78
|
+
If you prefer using an explicit path to your configuration file, pass the absolute path to the Listener constructor like so:
|
|
79
|
+
|
|
80
|
+
.. code-block:: python
|
|
81
|
+
|
|
82
|
+
from dnaStreaming.listener import Listener
|
|
83
|
+
# Config. file authentication
|
|
84
|
+
listener = Listener(config_file=<ABSOLUTE PATH TO YOUR CONFIG. FILE>)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
3. Pass in variables as function arguments.
|
|
88
|
+
###################################################################
|
|
89
|
+
|
|
90
|
+
You may pass your user key to the Listener constructor and your subscription ID to the listen method like so:
|
|
91
|
+
|
|
92
|
+
.. code-block:: python
|
|
93
|
+
|
|
94
|
+
from dnaStreaming.listener import Listener
|
|
95
|
+
# Use the user_key argument to provide your credentials
|
|
96
|
+
listener = Listener(user_key=<YOUR USER KEY>)
|
|
97
|
+
# Use the subscription_id argument to provide your subscription id to the listener
|
|
98
|
+
listener.listen(callback, subscription_id=<YOUR SUBSCRIPTION ID>)
|
|
99
|
+
# same parameter for the async variation
|
|
100
|
+
listener.listen_async(callback, subscription_id=<YOUR SUBSCRIPTION ID>)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
Or you may use the environment variables.
|
|
104
|
+
Remember that passing credentials and subscription ID(s) in this way will override the environment variable and the config file settings.
|
|
105
|
+
|
|
106
|
+
.. code-block:: python
|
|
107
|
+
|
|
108
|
+
from dnaStreaming.listener import Listener
|
|
109
|
+
|
|
110
|
+
listener = Listener()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
4. Listening to messages
|
|
114
|
+
###################################################################
|
|
115
|
+
|
|
116
|
+
You may want to listen messages synchronously like so:
|
|
117
|
+
|
|
118
|
+
.. code-block:: python
|
|
119
|
+
|
|
120
|
+
def callback(message, subscription_id):
|
|
121
|
+
print('Subscription ID: {}: Message: {}'.format(subscription_id, message.data.__str__()))
|
|
122
|
+
return True # If desired return False to stop the message flow. This will unblock the process as well.
|
|
123
|
+
|
|
124
|
+
listener.listen(callback, maximum_messages=10) # Omitting maximum_messages means you will continue to get messages as they appear. Can be a firehose. Use with caution.
|
|
125
|
+
# You may pass subscription ID as a parameter to the listen function
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
You may want to listen messages asynchronously like so:
|
|
129
|
+
|
|
130
|
+
.. code-block:: python
|
|
131
|
+
|
|
132
|
+
def callback(message, subscription_id):
|
|
133
|
+
print('Subscription ID: {}: Message: {}'.format(subscription_id, message.data.__str__()))
|
|
134
|
+
|
|
135
|
+
future = listener.listen_async(callback)
|
|
136
|
+
# After calling `listed_async` you need to keep the main thread alive.
|
|
137
|
+
|
|
138
|
+
for count in range(0, 5):
|
|
139
|
+
sleep(1)
|
|
140
|
+
|
|
141
|
+
# Stop receiving messages after 5 seconds
|
|
142
|
+
if future.running():
|
|
143
|
+
future.cancel()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
Log Files
|
|
147
|
+
_________
|
|
148
|
+
|
|
149
|
+
Very minimal logging is written to the module's path 'logs/dj-dna-streaming-python.log'. To keep maintenance simple this log is overwritten every time the app starts.
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
Testing
|
|
153
|
+
_______
|
|
154
|
+
|
|
155
|
+
.. code-block::
|
|
156
|
+
|
|
157
|
+
cd dnaStreaming/test
|
|
158
|
+
pip install -r requirements.txt
|
|
159
|
+
py.test . -s
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
or, alternatively, to test against python2.7 and python3.5:
|
|
163
|
+
|
|
164
|
+
.. code-block::
|
|
165
|
+
|
|
166
|
+
tox
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
Flake8
|
|
170
|
+
______
|
|
171
|
+
|
|
172
|
+
If you are maintaining this library, ensure you run flake8 before you commit. At project root command line:
|
|
173
|
+
|
|
174
|
+
.. code-block::
|
|
175
|
+
|
|
176
|
+
flake8 ./dnaStreaming ./tests
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
Running the Demonstration Code/Development
|
|
180
|
+
__________________________________________
|
|
181
|
+
|
|
182
|
+
If you are enhancing this codebase (and not just using it as a library), follow these example MacOS steps:
|
|
183
|
+
|
|
184
|
+
1. Checkout the Project from Git.
|
|
185
|
+
###################################################################
|
|
186
|
+
|
|
187
|
+
2. Go to the Project Root.
|
|
188
|
+
###################################################################
|
|
189
|
+
|
|
190
|
+
3. Create a Virtual Environment.
|
|
191
|
+
###################################################################
|
|
192
|
+
|
|
193
|
+
.. code-block::
|
|
194
|
+
|
|
195
|
+
virtualenv venv
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
4. Then activate the virutal environment by executing this command:
|
|
199
|
+
###################################################################
|
|
200
|
+
|
|
201
|
+
.. code-block::
|
|
202
|
+
|
|
203
|
+
source ./venv/bin/activate
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
5. Install the Dependencies
|
|
207
|
+
###################################################################
|
|
208
|
+
|
|
209
|
+
.. code-block::
|
|
210
|
+
|
|
211
|
+
pip install -r requirements.txt
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
6. Install the App:
|
|
215
|
+
###################################################################
|
|
216
|
+
|
|
217
|
+
.. code-block::
|
|
218
|
+
|
|
219
|
+
python setup.py install
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
7. Set the Configuration Variables
|
|
223
|
+
###################################################################
|
|
224
|
+
|
|
225
|
+
See the config section.
|
|
226
|
+
|
|
227
|
+
8. Run the Demo Code
|
|
228
|
+
###################################################################
|
|
229
|
+
|
|
230
|
+
Running Non-Docker Demo:
|
|
231
|
+
|
|
232
|
+
Execute the following at the project root:
|
|
233
|
+
|
|
234
|
+
.. code-block::
|
|
235
|
+
|
|
236
|
+
python ./dnaStreaming/demo/show_stream.py -s
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
Or
|
|
240
|
+
|
|
241
|
+
.. code-block::
|
|
242
|
+
|
|
243
|
+
python ./dnaStreaming/demo/show_stream_async.py -s
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
Running Docker Demo
|
|
247
|
+
|
|
248
|
+
Execute the following at the project root:
|
|
249
|
+
|
|
250
|
+
.. code-block::
|
|
251
|
+
|
|
252
|
+
docker run -it \
|
|
253
|
+
-e USER_KEY=<your user KEY> \
|
|
254
|
+
-e SUBSCRIPTION_ID=<your subscription ID> \
|
|
255
|
+
dj-dna-streaming-python
|
|
@@ -7,31 +7,32 @@ To use this we recommend 'pip installing' this by making the following addition
|
|
|
7
7
|
|
|
8
8
|
.. code-block::
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# To fetch latest version from PyPi
|
|
11
|
+
dnaStreaming
|
|
12
|
+
|
|
13
|
+
# to fetch latest version from GitHub
|
|
14
|
+
git+https://github.com/dowjones/dj-dna-streams-python#egg=dnaStreaming
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
Auth
|
|
14
18
|
-----------
|
|
15
19
|
|
|
16
|
-
There
|
|
17
|
-
|
|
18
|
-
- Your User Key
|
|
19
|
-
|
|
20
|
-
- UserId, ClientId, and Password
|
|
21
|
-
|
|
20
|
+
There is currently one way to authenticate, which is by using **your user key**.
|
|
22
21
|
|
|
23
22
|
Configuring
|
|
24
23
|
___________
|
|
25
24
|
|
|
26
|
-
To run this code, you need to provide credentials from one of the authentication methods and your subscriptions. There are 3 ways to do this
|
|
25
|
+
To run this code, you need to provide credentials from one of the authentication methods and your subscriptions. There are 3 ways to do this: you can set environment variables, use a configuration file or pass an argument to a constructor.
|
|
27
26
|
|
|
28
27
|
1. Set environment variables.
|
|
29
28
|
###################################################################
|
|
30
29
|
|
|
31
|
-
To set your service account credentials, set
|
|
30
|
+
To set your service account credentials, set the `USER_KEY` environment variable:
|
|
31
|
+
|
|
32
|
+
.. code-block::
|
|
33
|
+
|
|
34
|
+
export USER_KEY="1234567890-098765432"
|
|
32
35
|
|
|
33
|
-
- An environment variable named 'USER_KEY'.
|
|
34
|
-
- Three environment variable named 'USER_ID', 'CLIENT_ID', and 'PASSWORD'.
|
|
35
36
|
|
|
36
37
|
To set your subscription ID, simply set an environment variable named 'SUBSCRIPTION_ID' like so
|
|
37
38
|
|
|
@@ -45,21 +46,33 @@ The code above is the command line expression for setting this environment varia
|
|
|
45
46
|
2. Using the configuration file.
|
|
46
47
|
###################################################################
|
|
47
48
|
|
|
48
|
-
In this codebase you will find a file named 'customer_config.json'. You are not required to use this file. If you prefer to use this
|
|
49
|
+
In this codebase you will find a file named 'customer_config.json'. You are not required to use this file. If you prefer to use this option, fill the JSON object within by adding your user key and your subscription ID. Remember that this is a JSON file so follow basic JSON formatting and syntax conventions.
|
|
50
|
+
|
|
51
|
+
> The listener will search for the `customer_config.json` file inside your `$HOME` directory by default.
|
|
52
|
+
|
|
53
|
+
If you prefer using an explicit path to your configuration file, pass the absolute path to the Listener constructor like so:
|
|
54
|
+
|
|
55
|
+
.. code-block:: python
|
|
56
|
+
|
|
57
|
+
from dnaStreaming.listener import Listener
|
|
58
|
+
# Config. file authentication
|
|
59
|
+
listener = Listener(config_file=<ABSOLUTE PATH TO YOUR CONFIG. FILE>)
|
|
60
|
+
|
|
49
61
|
|
|
50
62
|
3. Pass in variables as function arguments.
|
|
51
63
|
###################################################################
|
|
52
64
|
|
|
53
|
-
You may pass your
|
|
65
|
+
You may pass your user key to the Listener constructor and your subscription ID to the listen method like so:
|
|
54
66
|
|
|
55
67
|
.. code-block:: python
|
|
56
68
|
|
|
57
69
|
from dnaStreaming.listener import Listener
|
|
58
|
-
#
|
|
70
|
+
# Use the user_key argument to provide your credentials
|
|
59
71
|
listener = Listener(user_key=<YOUR USER KEY>)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
# Use the subscription_id argument to provide your subscription id to the listener
|
|
73
|
+
listener.listen(callback, subscription_id=<YOUR SUBSCRIPTION ID>)
|
|
74
|
+
# same parameter for the async variation
|
|
75
|
+
listener.listen_async(callback, subscription_id=<YOUR SUBSCRIPTION ID>)
|
|
63
76
|
|
|
64
77
|
|
|
65
78
|
Or you may use the environment variables.
|
|
@@ -205,30 +218,10 @@ Or
|
|
|
205
218
|
python ./dnaStreaming/demo/show_stream_async.py -s
|
|
206
219
|
|
|
207
220
|
|
|
208
|
-
If you are having `ImportError: No module named ...` run this in your terminal before running the demo:
|
|
209
|
-
|
|
210
|
-
.. code-block::
|
|
211
|
-
|
|
212
|
-
export PYTHONPATH='.'
|
|
213
|
-
|
|
214
|
-
|
|
215
221
|
Running Docker Demo
|
|
216
222
|
|
|
217
223
|
Execute the following at the project root:
|
|
218
224
|
|
|
219
|
-
.. code-block::
|
|
220
|
-
|
|
221
|
-
docker build -f ./DockerfileDemo -t dj-dna-streaming-python .
|
|
222
|
-
|
|
223
|
-
docker run -it \
|
|
224
|
-
-e USER_ID=<your user ID> \
|
|
225
|
-
-e CLIENT_ID=<your client ID> \
|
|
226
|
-
-e PASSWORD=<your password> \
|
|
227
|
-
-e SUBSCRIPTION_ID=<your subscription ID> \
|
|
228
|
-
dj-dna-streaming-python
|
|
229
|
-
|
|
230
|
-
or:
|
|
231
|
-
|
|
232
225
|
.. code-block::
|
|
233
226
|
|
|
234
227
|
docker run -it \
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import absolute_import, division
|
|
2
|
+
|
|
3
|
+
import errno
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
class Config(object):
|
|
9
|
+
|
|
10
|
+
DEFAULT_HOST = 'https://api.dowjones.com'
|
|
11
|
+
|
|
12
|
+
DEFAULT_CUST_CONFIG_PATH = str(Path.home()) + '/customer_config.json'
|
|
13
|
+
ENV_VAR_SUBSCRIPTION_ID = 'SUBSCRIPTION_ID'
|
|
14
|
+
ENV_VAR_USER_KEY = 'USER_KEY'
|
|
15
|
+
ENV_VAR_SERVICE_ACCOUNT_ID = 'SERVICE_ACCOUNT_ID'
|
|
16
|
+
ENV_VAR_API_HOST = 'API_HOST'
|
|
17
|
+
|
|
18
|
+
def __init__(self, service_account_id=None, user_key=None, config_file=None):
|
|
19
|
+
self.customer_config_path = self.DEFAULT_CUST_CONFIG_PATH if config_file is None else config_file
|
|
20
|
+
self.initialized = False
|
|
21
|
+
self.service_account_id = service_account_id
|
|
22
|
+
self.user_key = user_key
|
|
23
|
+
|
|
24
|
+
self.headers = None
|
|
25
|
+
|
|
26
|
+
def _initialize(self):
|
|
27
|
+
self._validate()
|
|
28
|
+
|
|
29
|
+
with open(self.customer_config_path, 'r') as f:
|
|
30
|
+
self.customer_config = json.load(f)
|
|
31
|
+
|
|
32
|
+
self.initialized = True
|
|
33
|
+
self.headers = None
|
|
34
|
+
|
|
35
|
+
def _validate(self):
|
|
36
|
+
if not os.path.isfile(self.customer_config_path):
|
|
37
|
+
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), self.customer_config_path)
|
|
38
|
+
|
|
39
|
+
if not os.access(self.customer_config_path, os.R_OK):
|
|
40
|
+
raise Exception('Encountered permission problem reading file from path \'{}\'.'.format(self.customer_config_path))
|
|
41
|
+
|
|
42
|
+
def get_headers(self):
|
|
43
|
+
if self.headers:
|
|
44
|
+
return self.headers
|
|
45
|
+
else:
|
|
46
|
+
self.headers = self.get_authentication_headers()
|
|
47
|
+
return self.headers
|
|
48
|
+
|
|
49
|
+
def get_authentication_headers(self):
|
|
50
|
+
|
|
51
|
+
user_key = self.get_user_key()
|
|
52
|
+
if user_key:
|
|
53
|
+
return {
|
|
54
|
+
'user-key': user_key
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
else:
|
|
58
|
+
msg = """
|
|
59
|
+
Unable to find credentials. Specify your account credentials by choosing one of the following ways:
|
|
60
|
+
- set your user key to an env var under the name 'USER_KEY'
|
|
61
|
+
- place your customer_config.json file in your home directory (e.g. ~/, $HOME/)
|
|
62
|
+
- pass the absolute path of your customer_config.json file to the constructor of the Listener class
|
|
63
|
+
- pass the user key as a parameter to the constructor of the Listener class
|
|
64
|
+
"""
|
|
65
|
+
raise Exception(msg)
|
|
66
|
+
|
|
67
|
+
def get_uri_context(self):
|
|
68
|
+
host = os.getenv(self.ENV_VAR_API_HOST, self.DEFAULT_HOST)
|
|
69
|
+
return host
|
|
70
|
+
|
|
71
|
+
# in the following two methods, note that we use "SERVICE_ACCOUNT_ID" as a legacy,
|
|
72
|
+
# alternate name for the "USER_KEY" parameter, from the customer's perspective
|
|
73
|
+
def get_user_key(self):
|
|
74
|
+
user_key = self.user_key if self.user_key else self.service_account_id
|
|
75
|
+
|
|
76
|
+
if user_key is None:
|
|
77
|
+
user_key = os.getenv(self.ENV_VAR_USER_KEY,
|
|
78
|
+
os.getenv(self.ENV_VAR_SERVICE_ACCOUNT_ID))
|
|
79
|
+
|
|
80
|
+
if user_key is None:
|
|
81
|
+
user_key = self._user_key_id_from_file()
|
|
82
|
+
|
|
83
|
+
return user_key
|
|
84
|
+
|
|
85
|
+
def _user_key_id_from_file(self):
|
|
86
|
+
if not self.initialized:
|
|
87
|
+
self._initialize()
|
|
88
|
+
|
|
89
|
+
return self.customer_config.get('user_key',
|
|
90
|
+
self.customer_config.get('service_account_id'))
|
|
91
|
+
|
|
92
|
+
def subscription(self):
|
|
93
|
+
if os.getenv(self.ENV_VAR_SUBSCRIPTION_ID) is not None:
|
|
94
|
+
subscription = os.getenv(self.ENV_VAR_SUBSCRIPTION_ID)
|
|
95
|
+
else:
|
|
96
|
+
subscription = self._subscription_id_from_file()
|
|
97
|
+
|
|
98
|
+
return subscription
|
|
99
|
+
|
|
100
|
+
def _set_customer_config_path(self, path):
|
|
101
|
+
self.customer_config_path = path
|
|
102
|
+
self._initialize()
|
|
103
|
+
|
|
104
|
+
def _subscription_id_from_file(self):
|
|
105
|
+
if not self.initialized:
|
|
106
|
+
self._initialize()
|
|
107
|
+
|
|
108
|
+
return self.customer_config['subscription_id']
|
|
109
|
+
|
|
@@ -6,7 +6,12 @@ quiet_demo = os.getenv('QUIET_DEMO', "false") == "true"
|
|
|
6
6
|
print("\n[ACTIVITY] Receiving messages (SYNC)...\n[0]", end='')
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def callback(message, subscription_id
|
|
9
|
+
def callback(message, subscription_id):
|
|
10
|
+
# Replace the code below with the custom operations you'd like to apply to each article (message)
|
|
11
|
+
# like writing to a file, database, or pushing the message to a separate message broker system.
|
|
12
|
+
# This demo only prints some message details.
|
|
13
|
+
# Subscription id parameter is provided and can be used for auditing purposes.
|
|
14
|
+
|
|
10
15
|
callback.counter += 1
|
|
11
16
|
if not quiet_demo:
|
|
12
17
|
if message['action'] != 'del':
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
from threading import Thread
|
|
5
|
+
|
|
6
|
+
from google.api_core.exceptions import GoogleAPICallError, NotFound
|
|
7
|
+
from google import pubsub_v1
|
|
8
|
+
|
|
9
|
+
from dnaStreaming import logger
|
|
10
|
+
from dnaStreaming.config import Config
|
|
11
|
+
from dnaStreaming.services import pubsub_service, credentials_service
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Listener(object):
|
|
15
|
+
DEFAULT_UNLIMITED_MESSAGES = None
|
|
16
|
+
|
|
17
|
+
def __init__(self, service_account_id=None, user_key=None, config_file=None):
|
|
18
|
+
config = Config(service_account_id, user_key, config_file)
|
|
19
|
+
self._initialize(config)
|
|
20
|
+
self.current_subscription_index = 0
|
|
21
|
+
|
|
22
|
+
def _initialize(self, config):
|
|
23
|
+
self.config = config
|
|
24
|
+
|
|
25
|
+
def listen(self, on_message_callback, maximum_messages=DEFAULT_UNLIMITED_MESSAGES, subscription_id="", batch_size=10):
|
|
26
|
+
pubsub_client = pubsub_service.get_client(self.config)
|
|
27
|
+
|
|
28
|
+
subscription_id = subscription_id or self.config.subscription()
|
|
29
|
+
if not subscription_id:
|
|
30
|
+
raise Exception(
|
|
31
|
+
'No subscription specified. You must specify the subscription ID either through an environment ' +
|
|
32
|
+
'variable, a config file or by passing the value to the method.')
|
|
33
|
+
|
|
34
|
+
streaming_credentials = credentials_service.fetch_credentials(
|
|
35
|
+
self.config)
|
|
36
|
+
subscription_path = pubsub_client.subscription_path(
|
|
37
|
+
streaming_credentials['project_id'], subscription_id)
|
|
38
|
+
|
|
39
|
+
logger.info(
|
|
40
|
+
'Listeners for subscriptions have been configured, set and await message arrival.')
|
|
41
|
+
|
|
42
|
+
count = 0
|
|
43
|
+
while maximum_messages is None or count < maximum_messages:
|
|
44
|
+
try:
|
|
45
|
+
if maximum_messages is not None:
|
|
46
|
+
batch_size = min(batch_size, maximum_messages - count)
|
|
47
|
+
|
|
48
|
+
pull_request = pubsub_v1.PullRequest(
|
|
49
|
+
subscription=subscription_path,
|
|
50
|
+
max_messages=batch_size,
|
|
51
|
+
)
|
|
52
|
+
results = pubsub_client.pull(request=pull_request)
|
|
53
|
+
|
|
54
|
+
if results:
|
|
55
|
+
if len(results.received_messages) > 0:
|
|
56
|
+
for message in results.received_messages:
|
|
57
|
+
|
|
58
|
+
pubsub_msg = json.loads(message.message.data)
|
|
59
|
+
logger.info("Received news message with ID: {}".format(
|
|
60
|
+
pubsub_msg['data'][0]['id']))
|
|
61
|
+
news_msg = pubsub_msg['data'][0]['attributes']
|
|
62
|
+
|
|
63
|
+
keep_message_flow = on_message_callback(
|
|
64
|
+
news_msg, subscription_id)
|
|
65
|
+
|
|
66
|
+
if not keep_message_flow:
|
|
67
|
+
logger.info(
|
|
68
|
+
'Callback function returned False or falsy. Message flow stopped.')
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
ack_request = {
|
|
72
|
+
"subscription": subscription_path,
|
|
73
|
+
"ack_ids": [message.ack_id],
|
|
74
|
+
}
|
|
75
|
+
pubsub_client.acknowledge(request=ack_request)
|
|
76
|
+
count += 1
|
|
77
|
+
|
|
78
|
+
except GoogleAPICallError as e:
|
|
79
|
+
if isinstance(e, NotFound):
|
|
80
|
+
raise e
|
|
81
|
+
logger.error(
|
|
82
|
+
"Encountered a problem while trying to pull a message from a stream. Error is as follows: {}".format(str(e)))
|
|
83
|
+
logger.error("Due to the previous error, system will pause 10 seconds. System will then attempt to pull the message from "
|
|
84
|
+
"the stream again.")
|
|
85
|
+
time.sleep(10)
|
|
86
|
+
pubsub_client = pubsub_service.get_client(self.config)
|
|
87
|
+
|
|
88
|
+
def listen_async(self, on_message_callback, subscription_id=""):
|
|
89
|
+
def ack_message_and_callback(message):
|
|
90
|
+
pubsub_msg = json.loads(message.data)
|
|
91
|
+
logger.info("Received news message with ID: {}".format(
|
|
92
|
+
pubsub_msg['data'][0]['id']))
|
|
93
|
+
news_msg = pubsub_msg['data'][0]['attributes']
|
|
94
|
+
on_message_callback(news_msg, subscription_id)
|
|
95
|
+
message.ack()
|
|
96
|
+
|
|
97
|
+
pubsub_client = pubsub_service.get_client(self.config)
|
|
98
|
+
|
|
99
|
+
subscription_id = subscription_id or self.config.subscription()
|
|
100
|
+
|
|
101
|
+
if not subscription_id:
|
|
102
|
+
raise Exception(
|
|
103
|
+
'No subscription specified. You must specify the subscription ID either through an environment variable, a config file or '
|
|
104
|
+
'by passing the value to the method.')
|
|
105
|
+
|
|
106
|
+
streaming_credentials = credentials_service.fetch_credentials(
|
|
107
|
+
self.config)
|
|
108
|
+
subscription_path = pubsub_client.subscription_path(
|
|
109
|
+
streaming_credentials['project_id'], subscription_id)
|
|
110
|
+
subscription = pubsub_client.subscribe(
|
|
111
|
+
subscription_path, callback=ack_message_and_callback)
|
|
112
|
+
logger.info(
|
|
113
|
+
'Listeners for subscriptions have been configured, set and await message arrival.')
|
|
114
|
+
return subscription
|
|
@@ -5,7 +5,7 @@ import requests
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def fetch_credentials(config):
|
|
8
|
-
response = _get_requests().get(config.get_uri_context() + '/accounts/streaming-credentials',
|
|
8
|
+
response = _get_requests().get(config.get_uri_context() + '/sns-accounts/streaming-credentials',
|
|
9
9
|
headers=config.get_headers())
|
|
10
10
|
|
|
11
11
|
if response.status_code == 401:
|