Skip to content

Commit c145396

Browse files
jeffakolbAaron Gonzales
authored andcommitted
Header options (#49)
* Configure session not to trust env (#48) don't pick up .netrc dummy creds * Handle extra headers from config file or cmd-line * Set correct order in dict merge Command line should overwrite config/creds files * Update docs for custom header option * Update _version.py bump version * Fix naming for extra headers option
1 parent da5afeb commit c145396

File tree

9 files changed

+476
-394
lines changed

9 files changed

+476
-394
lines changed

README.rst

Lines changed: 389 additions & 363 deletions
Large diffs are not rendered by default.

examples/base_readme.rst

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ default location (``.twitter_keys.yaml``) or in environment variables.
103103
--filename-prefix beyonce_geo \
104104
--no-print-stream
105105
106+
One or more custom headers can be specified from the command line,
107+
using the ``--extra-headers`` argument and a JSON-formatted string
108+
representing a dictionary of extra headers:
109+
110+
.. code:: bash
111+
112+
search_tweets.py \
113+
--filter-rule "beyonce has:hashtags" \
114+
--extra-headers '{"<MY_HEADER_KEY>":"<MY_HEADER_VALUE>"}'
115+
106116
107117
Options can be passed via a configuration file (either ini or YAML). Example
108118
files can be found in the ``tools/api_config_example.config`` or
@@ -142,13 +152,26 @@ Or this:
142152
filename_prefix: kanye
143153
results_per_file: 10000000
144154
155+
Custom headers can be specified in a config file, under a specific credentials
156+
key:
157+
158+
.. code:: yaml
159+
160+
search_tweets_api:
161+
account_type: premium
162+
endpoint: <FULL_URL_OF_ENDPOINT>
163+
username: <USERNAME>
164+
password: <PW>
165+
extra_headers:
166+
<MY_HEADER_KEY>: <MY_HEADER_VALUE>
145167
146168
When using a config file in conjunction with the command-line utility, you need
147169
to specify your config file via the ``--config-file`` parameter. Additional
148170
command-line arguments will either be *added* to the config file args or
149171
**overwrite** the config file args if both are specified and present.
150172

151173

174+
152175
Example::
153176

154177
search_tweets.py \
@@ -175,7 +198,8 @@ Full options are listed below:
175198
[--max-results MAX_RESULTS] [--max-pages MAX_PAGES]
176199
[--results-per-file RESULTS_PER_FILE]
177200
[--filename-prefix FILENAME_PREFIX]
178-
[--no-print-stream] [--print-stream] [--debug]
201+
[--no-print-stream] [--print-stream]
202+
[--extra-headers EXTRA_HEADERS] [--debug]
179203

180204
optional arguments:
181205
-h, --help show this help message and exit
@@ -224,7 +248,10 @@ Full options are listed below:
224248
prefix for the filename where tweet json data will be
225249
stored.
226250
--no-print-stream disable print streaming
227-
--print-stream Print tweet stream to stdout
251+
--print-stream Print tweet stream to stdout
252+
--extra-headers EXTRA_HEADERS
253+
JSON-formatted str representing a dict of additional
254+
request headers
228255
--debug print all info and warning messages
229256

230257

examples/credential_handling.ipynb

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,29 +112,27 @@
112112
},
113113
{
114114
"cell_type": "code",
115-
"execution_count": 10,
116-
"metadata": {
117-
"collapsed": true
118-
},
115+
"execution_count": 5,
116+
"metadata": {},
119117
"outputs": [],
120118
"source": [
121119
"from searchtweets import load_credentials"
122120
]
123121
},
124122
{
125123
"cell_type": "code",
126-
"execution_count": 14,
124+
"execution_count": 6,
127125
"metadata": {},
128126
"outputs": [
129127
{
130128
"data": {
131129
"text/plain": [
132-
"{'endpoint': '<MY_ENDPOINT>',\n",
130+
"{'username': '<MY_USERNAME>',\n",
133131
" 'password': '<MY_PASSWORD>',\n",
134-
" 'username': '<MY_USERNAME>'}"
132+
" 'endpoint': '<MY_ENDPOINT>'}"
135133
]
136134
},
137-
"execution_count": 14,
135+
"execution_count": 6,
138136
"metadata": {},
139137
"output_type": "execute_result"
140138
}
@@ -147,17 +145,18 @@
147145
},
148146
{
149147
"cell_type": "code",
150-
"execution_count": 15,
148+
"execution_count": 7,
151149
"metadata": {},
152150
"outputs": [
153151
{
154152
"data": {
155153
"text/plain": [
156154
"{'bearer_token': '<A_VERY_LONG_MAGIC_STRING>',\n",
157-
" 'endpoint': 'https://api.twitter.com/1.1/tweets/search/30day/dev.json'}"
155+
" 'endpoint': 'https://api.twitter.com/1.1/tweets/search/30day/dev.json',\n",
156+
" 'extra_headers_dict': None}"
158157
]
159158
},
160-
"execution_count": 15,
159+
"execution_count": 7,
161160
"metadata": {},
162161
"output_type": "execute_result"
163162
}
@@ -179,7 +178,7 @@
179178
},
180179
{
181180
"cell_type": "code",
182-
"execution_count": 16,
181+
"execution_count": 8,
183182
"metadata": {},
184183
"outputs": [
185184
{
@@ -193,12 +192,12 @@
193192
{
194193
"data": {
195194
"text/plain": [
196-
"{'endpoint': '<https://endpoint>',\n",
195+
"{'username': '<ENV_USERNAME>',\n",
197196
" 'password': '<ENV_PW>',\n",
198-
" 'username': '<ENV_USERNAME>'}"
197+
" 'endpoint': '<https://endpoint>'}"
199198
]
200199
},
201-
"execution_count": 16,
200+
"execution_count": 8,
202201
"metadata": {},
203202
"output_type": "execute_result"
204203
}
@@ -226,6 +225,13 @@
226225
"\n",
227226
"are used to control credential behavior from the command-line app. "
228227
]
228+
},
229+
{
230+
"cell_type": "code",
231+
"execution_count": null,
232+
"metadata": {},
233+
"outputs": [],
234+
"source": []
229235
}
230236
],
231237
"metadata": {
@@ -244,7 +250,7 @@
244250
"name": "python",
245251
"nbconvert_exporter": "python",
246252
"pygments_lexer": "ipython3",
247-
"version": "3.6.1"
253+
"version": "3.7.0"
248254
}
249255
},
250256
"nbformat": 4,

examples/credential_handling.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ library.
137137
138138
::
139139

140-
{'endpoint': '<MY_ENDPOINT>',
140+
{'username': '<MY_USERNAME>',
141141
'password': '<MY_PASSWORD>',
142-
'username': '<MY_USERNAME>'}
142+
'endpoint': '<MY_ENDPOINT>'}
143143

144144

145145

@@ -155,7 +155,8 @@ library.
155155
::
156156

157157
{'bearer_token': '<A_VERY_LONG_MAGIC_STRING>',
158-
'endpoint': 'https://api.twitter.com/1.1/tweets/search/30day/dev.json'}
158+
'endpoint': 'https://api.twitter.com/1.1/tweets/search/30day/dev.json',
159+
'extra_headers_dict': None}
159160

160161

161162

@@ -185,9 +186,9 @@ regardless of a YAML file's validity or existence.
185186

186187
::
187188

188-
{'endpoint': '<https://endpoint>',
189+
{'username': '<ENV_USERNAME>',
189190
'password': '<ENV_PW>',
190-
'username': '<ENV_USERNAME>'}
191+
'endpoint': '<https://endpoint>'}
191192

192193

193194

@@ -201,3 +202,4 @@ the flags:
201202
- ``--env-overwrite``
202203

203204
are used to control credential behavior from the command-line app.
205+

searchtweets/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# Copyright 2018 Twitter, Inc.
33
# Licensed under the MIT License
44
# https://opensource.org/licenses/MIT
5-
VERSION = "1.7.2"
5+
VERSION = "1.7.4"

searchtweets/api_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def intify(arg):
171171
"username": config_dict.get("username"),
172172
"password": config_dict.get("password"),
173173
"bearer_token": config_dict.get("bearer_token"),
174+
"extra_headers_dict": config_dict.get("extra_headers_dict",None),
174175
"rule_payload": rule,
175176
"results_per_file": intify(config_dict.get("results_per_file")),
176177
"max_results": intify(config_dict.get("max_results")),

searchtweets/credentials.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ def _parse_credentials(search_creds, account_type):
9292

9393
search_args = {
9494
"bearer_token": search_creds["bearer_token"],
95-
"endpoint": search_creds["endpoint"]}
95+
"endpoint": search_creds["endpoint"],
96+
"extra_headers_dict": search_creds.get("extra_headers",None)}
9697
if account_type == "enterprise":
9798
search_args = {"username": search_creds["username"],
9899
"password": search_creds["password"],
@@ -123,6 +124,8 @@ def load_credentials(filename=None, account_type=None,
123124
consumer_secret: <SECRET>
124125
bearer_token: <TOKEN>
125126
account_type: <enterprise OR premium>
127+
extra_headers:
128+
<MY_HEADER_KEY>: <MY_HEADER_VALUE>
126129
127130
with the appropriate fields filled out for your account. The top-level key
128131
defaults to ``search_tweets_api`` but can be flexible.

searchtweets/result_stream.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
logger = logging.getLogger(__name__)
2929

3030

31-
def make_session(username=None, password=None, bearer_token=None):
31+
def make_session(username=None, password=None, bearer_token=None, extra_headers_dict=None):
3232
"""Creates a Requests Session for use. Accepts a bearer token
3333
for premiums users and will override username and password information if
3434
present.
@@ -56,6 +56,8 @@ def make_session(username=None, password=None, bearer_token=None):
5656
logger.info("using username and password for authentication")
5757
session.auth = username, password
5858
session.headers = headers
59+
if extra_headers_dict:
60+
headers.update(extra_headers_dict)
5961
return session
6062

6163

@@ -144,7 +146,9 @@ class ResultStream:
144146
tweet parser library to convert each raw tweet package to a Tweet
145147
with lazy properties.
146148
max_requests (int): A hard cutoff for the number of API calls this
147-
instance will make. Good for testing in sandbox premium environments.
149+
instance will make. Good for testing in sandbox premium environments.
150+
extra_headers_dict (dict): custom headers to add
151+
148152
149153
Example:
150154
>>> rs = ResultStream(**search_args, rule_payload=rule, max_pages=1)
@@ -156,12 +160,13 @@ class ResultStream:
156160
session_request_counter = 0
157161

158162
def __init__(self, endpoint, rule_payload, username=None, password=None,
159-
bearer_token=None, max_results=500,
163+
bearer_token=None, extra_headers_dict=None, max_results=500,
160164
tweetify=True, max_requests=None, **kwargs):
161165

162166
self.username = username
163167
self.password = password
164168
self.bearer_token = bearer_token
169+
self.extra_headers_dict = extra_headers_dict
165170
if isinstance(rule_payload, str):
166171
rule_payload = json.loads(rule_payload)
167172
self.rule_payload = rule_payload
@@ -229,7 +234,8 @@ def init_session(self):
229234
self.session.close()
230235
self.session = make_session(self.username,
231236
self.password,
232-
self.bearer_token)
237+
self.bearer_token,
238+
self.extra_headers_dict)
233239

234240
def check_counts(self):
235241
"""

tools/search_tweets.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ def parse_cmd_args():
126126
default=True,
127127
help="Print tweet stream to stdout")
128128

129+
argparser.add_argument("--extra-headers",
130+
dest="extra_headers",
131+
type=str,
132+
default=None,
133+
help="JSON-formatted str representing a dict of additional request headers")
134+
129135
argparser.add_argument("--debug",
130136
dest="debug",
131137
action="store_true",
@@ -149,6 +155,11 @@ def main():
149155
configfile_dict = read_config(args_dict["config_filename"])
150156
else:
151157
configfile_dict = {}
158+
159+
extra_headers_str = args_dict.get("extra_headers")
160+
if extra_headers_str is not None:
161+
args_dict['extra_headers_dict'] = json.loads(extra_headers_str)
162+
del args_dict['extra_headers']
152163

153164
logger.debug("config file ({}) arguments sans sensitive args:".format(args_dict["config_filename"]))
154165
logger.debug(json.dumps(_filter_sensitive_args(configfile_dict), indent=4))
@@ -161,8 +172,8 @@ def main():
161172
dict_filter = lambda x: {k: v for k, v in x.items() if v is not None}
162173

163174
config_dict = merge_dicts(dict_filter(configfile_dict),
164-
dict_filter(args_dict),
165-
dict_filter(creds_dict))
175+
dict_filter(creds_dict),
176+
dict_filter(args_dict))
166177

167178
logger.debug("combined dict (cli, config, creds) sans password:")
168179
logger.debug(json.dumps(_filter_sensitive_args(config_dict), indent=4))

0 commit comments

Comments
 (0)