datamule 1.1.7__py3-none-any.whl → 1.2.0__py3-none-any.whl
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.
- datamule/document.py +231 -29
- datamule/portfolio.py +2 -4
- datamule/sec/submissions/monitor.py +5 -1
- datamule/seclibrary/bq.py +528 -0
- datamule/sheet.py +644 -13
- datamule/submission.py +2 -1
- {datamule-1.1.7.dist-info → datamule-1.2.0.dist-info}/METADATA +1 -1
- {datamule-1.1.7.dist-info → datamule-1.2.0.dist-info}/RECORD +10 -9
- {datamule-1.1.7.dist-info → datamule-1.2.0.dist-info}/WHEEL +1 -1
- {datamule-1.1.7.dist-info → datamule-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,528 @@
|
|
1
|
+
import os
|
2
|
+
import requests
|
3
|
+
import json
|
4
|
+
|
5
|
+
def get_information_table(
|
6
|
+
# Optional filtering parameters
|
7
|
+
columns=None,
|
8
|
+
name_of_issuer=None,
|
9
|
+
title_of_class=None,
|
10
|
+
cusip=None,
|
11
|
+
value=None,
|
12
|
+
ssh_prnamt=None,
|
13
|
+
ssh_prnamt_type=None,
|
14
|
+
investment_discretion=None,
|
15
|
+
voting_authority_sole=None,
|
16
|
+
voting_authority_shared=None,
|
17
|
+
voting_authority_none=None,
|
18
|
+
reporting_owner_cik=None,
|
19
|
+
put_call=None,
|
20
|
+
other_manager=None,
|
21
|
+
figi=None,
|
22
|
+
accession=None,
|
23
|
+
filing_date=None,
|
24
|
+
|
25
|
+
# API key handling
|
26
|
+
api_key=None,
|
27
|
+
|
28
|
+
# Additional options
|
29
|
+
print_cost=True,
|
30
|
+
verbose=False
|
31
|
+
):
|
32
|
+
"""
|
33
|
+
Query the SEC BigQuery API for 13F-HR information table data.
|
34
|
+
|
35
|
+
Parameters:
|
36
|
+
-----------
|
37
|
+
columns : List[str], optional
|
38
|
+
Specific columns to return. If None, all columns are returned.
|
39
|
+
|
40
|
+
# Filter parameters
|
41
|
+
name_of_issuer, title_of_class, etc. : Various filters that can be:
|
42
|
+
- str: Exact match
|
43
|
+
- List[str]: Match any in list
|
44
|
+
- tuple: (min, max) range for numeric/date fields
|
45
|
+
|
46
|
+
api_key : str, optional
|
47
|
+
SEC BigQuery API key. If None, looks for DATAMULE_API_KEY env variable.
|
48
|
+
print_cost : bool
|
49
|
+
Whether to print the query cost information
|
50
|
+
verbose : bool
|
51
|
+
Whether to print additional information about the query
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
--------
|
55
|
+
List[Dict]
|
56
|
+
A list of dictionaries containing the query results
|
57
|
+
|
58
|
+
Raises:
|
59
|
+
-------
|
60
|
+
ValueError
|
61
|
+
If API key is missing or invalid
|
62
|
+
Exception
|
63
|
+
For API errors or other issues
|
64
|
+
"""
|
65
|
+
|
66
|
+
# 1. Handle API key
|
67
|
+
if api_key is None:
|
68
|
+
api_key = os.getenv('DATAMULE_API_KEY')
|
69
|
+
|
70
|
+
if not api_key:
|
71
|
+
raise ValueError("No API key found. Please set DATAMULE_API_KEY environment variable or provide api_key parameter")
|
72
|
+
|
73
|
+
# 2. Build query parameters
|
74
|
+
params = {'table_type': 'INFORMATION_TABLE'}
|
75
|
+
|
76
|
+
# Add columns parameter if provided
|
77
|
+
if columns:
|
78
|
+
if isinstance(columns, list):
|
79
|
+
params['columns'] = ','.join(columns)
|
80
|
+
else:
|
81
|
+
params['columns'] = columns
|
82
|
+
|
83
|
+
# Map Python parameter names to API parameter names
|
84
|
+
param_mapping = {
|
85
|
+
'name_of_issuer': 'nameOfIssuer',
|
86
|
+
'title_of_class': 'titleOfClass',
|
87
|
+
'cusip': 'cusip',
|
88
|
+
'value': 'value',
|
89
|
+
'ssh_prnamt': 'sshPrnamt',
|
90
|
+
'ssh_prnamt_type': 'sshPrnamtType',
|
91
|
+
'investment_discretion': 'investmentDiscretion',
|
92
|
+
'voting_authority_sole': 'votingAuthoritySole',
|
93
|
+
'voting_authority_shared': 'votingAuthorityShared',
|
94
|
+
'voting_authority_none': 'votingAuthorityNone',
|
95
|
+
'reporting_owner_cik': 'reportingOwnerCIK',
|
96
|
+
'put_call': 'putCall',
|
97
|
+
'other_manager': 'otherManager',
|
98
|
+
'figi': 'figi',
|
99
|
+
'accession': 'accession',
|
100
|
+
'filing_date': 'filingDate'
|
101
|
+
}
|
102
|
+
|
103
|
+
# Process all possible filter parameters
|
104
|
+
for param_name, api_param_name in param_mapping.items():
|
105
|
+
value = locals()[param_name]
|
106
|
+
if value is not None:
|
107
|
+
# Handle different filter types
|
108
|
+
if isinstance(value, list):
|
109
|
+
# List filter
|
110
|
+
params[api_param_name] = f"[{','.join(str(v) for v in value)}]"
|
111
|
+
elif isinstance(value, tuple):
|
112
|
+
# Range filter
|
113
|
+
if len(value) == 2:
|
114
|
+
min_val, max_val = value
|
115
|
+
# Handle date range specially
|
116
|
+
if param_name == 'filing_date':
|
117
|
+
# Dates need to be in quotes within the parentheses
|
118
|
+
if min_val is None:
|
119
|
+
min_val = ''
|
120
|
+
else:
|
121
|
+
min_val = f"'{min_val}'"
|
122
|
+
|
123
|
+
if max_val is None:
|
124
|
+
max_val = ''
|
125
|
+
else:
|
126
|
+
max_val = f"'{max_val}'"
|
127
|
+
|
128
|
+
range_str = f"({min_val},{max_val})"
|
129
|
+
params[api_param_name] = range_str
|
130
|
+
else:
|
131
|
+
raise ValueError(f"Range filter for {param_name} must be a tuple of (min, max)")
|
132
|
+
else:
|
133
|
+
# Exact match
|
134
|
+
params[api_param_name] = value
|
135
|
+
|
136
|
+
# Call common function to make API request
|
137
|
+
return _make_api_request(params, api_key, print_cost, verbose)
|
138
|
+
|
139
|
+
def get_345(
|
140
|
+
# Optional filtering parameters
|
141
|
+
columns=None,
|
142
|
+
is_derivative=None,
|
143
|
+
is_non_derivative=None,
|
144
|
+
security_title=None,
|
145
|
+
transaction_date=None,
|
146
|
+
document_type=None,
|
147
|
+
transaction_code=None,
|
148
|
+
equity_swap_involved=None,
|
149
|
+
transaction_timeliness=None,
|
150
|
+
transaction_shares=None,
|
151
|
+
transaction_price_per_share=None,
|
152
|
+
shares_owned_following_transaction=None,
|
153
|
+
ownership_type=None,
|
154
|
+
deemed_execution_date=None,
|
155
|
+
conversion_or_exercise_price=None,
|
156
|
+
exercise_date=None,
|
157
|
+
expiration_date=None,
|
158
|
+
underlying_security_title=None,
|
159
|
+
underlying_security_shares=None,
|
160
|
+
underlying_security_value=None,
|
161
|
+
accession=None,
|
162
|
+
reporting_owner_cik=None,
|
163
|
+
issuer_cik=None,
|
164
|
+
filing_date=None,
|
165
|
+
|
166
|
+
# API key handling
|
167
|
+
api_key=None,
|
168
|
+
|
169
|
+
# Additional options
|
170
|
+
print_cost=True,
|
171
|
+
verbose=False
|
172
|
+
):
|
173
|
+
"""
|
174
|
+
Query the SEC BigQuery API for Form 345 insider transaction data.
|
175
|
+
|
176
|
+
Parameters:
|
177
|
+
-----------
|
178
|
+
columns : List[str], optional
|
179
|
+
Specific columns to return. If None, all columns are returned.
|
180
|
+
|
181
|
+
# Filter parameters
|
182
|
+
is_derivative, security_title, etc. : Various filters that can be:
|
183
|
+
- str/bool: Exact match
|
184
|
+
- List[str]: Match any in list
|
185
|
+
- tuple: (min, max) range for numeric/date fields
|
186
|
+
|
187
|
+
reporting_owner_cik : str or List[str]
|
188
|
+
CIK(s) of the reporting insider(s). This is matched against an array in BigQuery.
|
189
|
+
Any match within the array will return the record.
|
190
|
+
|
191
|
+
issuer_cik : str or List[str]
|
192
|
+
CIK(s) of the company/companies
|
193
|
+
|
194
|
+
api_key : str, optional
|
195
|
+
SEC BigQuery API key. If None, looks for DATAMULE_API_KEY env variable.
|
196
|
+
print_cost : bool
|
197
|
+
Whether to print the query cost information
|
198
|
+
verbose : bool
|
199
|
+
Whether to print additional information about the query
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
--------
|
203
|
+
List[Dict]
|
204
|
+
A list of dictionaries containing the query results
|
205
|
+
|
206
|
+
Raises:
|
207
|
+
-------
|
208
|
+
ValueError
|
209
|
+
If API key is missing or invalid
|
210
|
+
Exception
|
211
|
+
For API errors or other issues
|
212
|
+
"""
|
213
|
+
|
214
|
+
# 1. Handle API key
|
215
|
+
if api_key is None:
|
216
|
+
api_key = os.getenv('DATAMULE_API_KEY')
|
217
|
+
|
218
|
+
if not api_key:
|
219
|
+
raise ValueError("No API key found. Please set DATAMULE_API_KEY environment variable or provide api_key parameter")
|
220
|
+
|
221
|
+
# 2. Build query parameters
|
222
|
+
params = {'table_type': 'FORM_345_TABLE'}
|
223
|
+
|
224
|
+
# Add columns parameter if provided
|
225
|
+
if columns:
|
226
|
+
if isinstance(columns, list):
|
227
|
+
params['columns'] = ','.join(columns)
|
228
|
+
else:
|
229
|
+
params['columns'] = columns
|
230
|
+
|
231
|
+
# Map Python parameter names to API parameter names
|
232
|
+
param_mapping = {
|
233
|
+
'is_derivative': 'isDerivative',
|
234
|
+
'is_non_derivative': 'isNonDerivative',
|
235
|
+
'security_title': 'securityTitle',
|
236
|
+
'transaction_date': 'transactionDate',
|
237
|
+
'document_type': 'documentType',
|
238
|
+
'transaction_code': 'transactionCode',
|
239
|
+
'equity_swap_involved': 'equitySwapInvolved',
|
240
|
+
'transaction_timeliness': 'transactionTimeliness',
|
241
|
+
'transaction_shares': 'transactionShares',
|
242
|
+
'transaction_price_per_share': 'transactionPricePerShare',
|
243
|
+
'shares_owned_following_transaction': 'sharesOwnedFollowingTransaction',
|
244
|
+
'ownership_type': 'ownershipType',
|
245
|
+
'deemed_execution_date': 'deemedExecutionDate',
|
246
|
+
'conversion_or_exercise_price': 'conversionOrExercisePrice',
|
247
|
+
'exercise_date': 'exerciseDate',
|
248
|
+
'expiration_date': 'expirationDate',
|
249
|
+
'underlying_security_title': 'underlyingSecurityTitle',
|
250
|
+
'underlying_security_shares': 'underlyingSecurityShares',
|
251
|
+
'underlying_security_value': 'underlyingSecurityValue',
|
252
|
+
'accession': 'accession',
|
253
|
+
'reporting_owner_cik': 'reportingOwnerCIK',
|
254
|
+
'issuer_cik': 'issuerCIK',
|
255
|
+
'filing_date': 'filingDate'
|
256
|
+
}
|
257
|
+
|
258
|
+
# Process all possible filter parameters
|
259
|
+
date_params = ['transaction_date', 'filing_date', 'deemed_execution_date', 'exercise_date', 'expiration_date']
|
260
|
+
boolean_params = ['is_derivative', 'is_non_derivative']
|
261
|
+
|
262
|
+
for param_name, api_param_name in param_mapping.items():
|
263
|
+
value = locals()[param_name]
|
264
|
+
if value is not None:
|
265
|
+
# Handle different filter types
|
266
|
+
if isinstance(value, list):
|
267
|
+
# List filter
|
268
|
+
params[api_param_name] = f"[{','.join(str(v) for v in value)}]"
|
269
|
+
elif isinstance(value, tuple):
|
270
|
+
# Range filter
|
271
|
+
if len(value) == 2:
|
272
|
+
min_val, max_val = value
|
273
|
+
# Handle date range specially
|
274
|
+
if param_name in date_params:
|
275
|
+
# Dates need to be in quotes within the parentheses
|
276
|
+
if min_val is None:
|
277
|
+
min_val = ''
|
278
|
+
else:
|
279
|
+
min_val = f"'{min_val}'"
|
280
|
+
|
281
|
+
if max_val is None:
|
282
|
+
max_val = ''
|
283
|
+
else:
|
284
|
+
max_val = f"'{max_val}'"
|
285
|
+
|
286
|
+
range_str = f"({min_val},{max_val})"
|
287
|
+
params[api_param_name] = range_str
|
288
|
+
else:
|
289
|
+
raise ValueError(f"Range filter for {param_name} must be a tuple of (min, max)")
|
290
|
+
elif param_name in boolean_params:
|
291
|
+
# Boolean values
|
292
|
+
params[api_param_name] = str(value).lower()
|
293
|
+
else:
|
294
|
+
# Exact match
|
295
|
+
params[api_param_name] = value
|
296
|
+
|
297
|
+
# Call common function to make API request
|
298
|
+
return _make_api_request(params, api_key, print_cost, verbose)
|
299
|
+
|
300
|
+
def _make_api_request(params, api_key, print_cost=True, verbose=False):
|
301
|
+
"""
|
302
|
+
Common function to make API requests to the SEC BigQuery API.
|
303
|
+
|
304
|
+
Parameters:
|
305
|
+
-----------
|
306
|
+
params : dict
|
307
|
+
Query parameters
|
308
|
+
api_key : str
|
309
|
+
API key for authentication
|
310
|
+
print_cost : bool
|
311
|
+
Whether to print cost information
|
312
|
+
verbose : bool
|
313
|
+
Whether to print debugging information
|
314
|
+
|
315
|
+
Returns:
|
316
|
+
--------
|
317
|
+
List[Dict]
|
318
|
+
Data returned from the API
|
319
|
+
|
320
|
+
Raises:
|
321
|
+
-------
|
322
|
+
ValueError
|
323
|
+
If API key is invalid
|
324
|
+
Exception
|
325
|
+
For other API errors
|
326
|
+
"""
|
327
|
+
# Make the API request
|
328
|
+
BASE_URL = "https://sec-bq.jgfriedman99.workers.dev/"
|
329
|
+
|
330
|
+
headers = {
|
331
|
+
'Authorization': f'Bearer {api_key}',
|
332
|
+
'Accept': 'application/json'
|
333
|
+
}
|
334
|
+
|
335
|
+
if verbose:
|
336
|
+
print(f"Making request to {BASE_URL} with params: {params}")
|
337
|
+
|
338
|
+
try:
|
339
|
+
response = requests.get(BASE_URL, params=params, headers=headers)
|
340
|
+
|
341
|
+
# Check for HTTP errors
|
342
|
+
response.raise_for_status()
|
343
|
+
|
344
|
+
# Parse response
|
345
|
+
result = response.json()
|
346
|
+
|
347
|
+
# Check for API-level errors
|
348
|
+
if not result.get('success', False):
|
349
|
+
error_msg = result.get('error', 'Unknown API error')
|
350
|
+
raise Exception(f"API Error: {error_msg}")
|
351
|
+
|
352
|
+
# Extract metadata for cost reporting
|
353
|
+
metadata = result.get('metadata', {})
|
354
|
+
|
355
|
+
# Process the data to handle array fields
|
356
|
+
data = result.get('data', [])
|
357
|
+
for row in data:
|
358
|
+
# Check if reportingOwnerCIK is an array that needs processing
|
359
|
+
if 'reportingOwnerCIK' in row and isinstance(row['reportingOwnerCIK'], list):
|
360
|
+
# Transform from [{'v': 'value1'}, {'v': 'value2'}] to comma-separated string
|
361
|
+
row['reportingOwnerCIK'] = ','.join([item['v'] for item in row['reportingOwnerCIK'] if 'v' in item])
|
362
|
+
|
363
|
+
# Print cost information if requested
|
364
|
+
if print_cost and 'billing' in metadata:
|
365
|
+
billing = metadata['billing']
|
366
|
+
query_info = metadata.get('query_info', {})
|
367
|
+
|
368
|
+
print("\n=== Query Cost Information ===")
|
369
|
+
print(f"Bytes Processed: {query_info.get('bytes_processed', 0):,} bytes")
|
370
|
+
print(f"Data Processed: {billing.get('tb_processed', 0):.10f} TB")
|
371
|
+
print(f"Cost Rate: ${billing.get('cost_per_tb', 0):.2f}/TB")
|
372
|
+
print(f"Query Cost: ${billing.get('total_charge', 0):.6f}")
|
373
|
+
print(f"Remaining Balance: ${billing.get('remaining_balance', 0):.2f}")
|
374
|
+
print(f"Execution Time: {query_info.get('execution_time_ms', 0)} ms")
|
375
|
+
print(f"Cache Hit: {query_info.get('cache_hit', False)}")
|
376
|
+
print("==============================\n")
|
377
|
+
|
378
|
+
# Return data
|
379
|
+
return data
|
380
|
+
|
381
|
+
except requests.exceptions.RequestException as e:
|
382
|
+
if hasattr(e, 'response') and e.response is not None and e.response.status_code == 401:
|
383
|
+
raise ValueError("Authentication failed: Invalid API key")
|
384
|
+
else:
|
385
|
+
raise Exception(f"Request failed: {str(e)}")
|
386
|
+
|
387
|
+
def get_proxy_voting_record(
|
388
|
+
# Optional filtering parameters
|
389
|
+
columns=None,
|
390
|
+
meeting_date=None,
|
391
|
+
isin=None,
|
392
|
+
cusip=None,
|
393
|
+
issuer_name=None,
|
394
|
+
vote_description=None,
|
395
|
+
shares_on_loan=None,
|
396
|
+
shares_voted=None,
|
397
|
+
vote_category=None,
|
398
|
+
vote_record=None,
|
399
|
+
vote_source=None,
|
400
|
+
how_voted=None,
|
401
|
+
figi=None,
|
402
|
+
management_recommendation=None,
|
403
|
+
accession=None,
|
404
|
+
reporting_owner_cik=None,
|
405
|
+
filing_date=None,
|
406
|
+
|
407
|
+
# API key handling
|
408
|
+
api_key=None,
|
409
|
+
|
410
|
+
# Additional options
|
411
|
+
print_cost=True,
|
412
|
+
verbose=False
|
413
|
+
):
|
414
|
+
"""
|
415
|
+
Query the SEC BigQuery API for NPX proxy voting record data.
|
416
|
+
|
417
|
+
Parameters:
|
418
|
+
-----------
|
419
|
+
columns : List[str], optional
|
420
|
+
Specific columns to return. If None, all columns are returned.
|
421
|
+
|
422
|
+
# Filter parameters
|
423
|
+
meeting_date, isin, cusip, etc. : Various filters that can be:
|
424
|
+
- str: Exact match
|
425
|
+
- List[str]: Match any in list
|
426
|
+
- tuple: (min, max) range for numeric/date fields
|
427
|
+
|
428
|
+
shares_on_loan, shares_voted : int/float or tuple
|
429
|
+
Numeric values or (min, max) range
|
430
|
+
|
431
|
+
filing_date : str or tuple
|
432
|
+
Date string in 'YYYY-MM-DD' format or (start_date, end_date) tuple
|
433
|
+
|
434
|
+
api_key : str, optional
|
435
|
+
SEC BigQuery API key. If None, looks for DATAMULE_API_KEY env variable.
|
436
|
+
print_cost : bool
|
437
|
+
Whether to print the query cost information
|
438
|
+
verbose : bool
|
439
|
+
Whether to print additional information about the query
|
440
|
+
|
441
|
+
Returns:
|
442
|
+
--------
|
443
|
+
List[Dict]
|
444
|
+
A list of dictionaries containing the query results
|
445
|
+
|
446
|
+
Raises:
|
447
|
+
-------
|
448
|
+
ValueError
|
449
|
+
If API key is missing or invalid
|
450
|
+
Exception
|
451
|
+
For API errors or other issues
|
452
|
+
"""
|
453
|
+
|
454
|
+
# 1. Handle API key
|
455
|
+
if api_key is None:
|
456
|
+
api_key = os.getenv('DATAMULE_API_KEY')
|
457
|
+
|
458
|
+
if not api_key:
|
459
|
+
raise ValueError("No API key found. Please set DATAMULE_API_KEY environment variable or provide api_key parameter")
|
460
|
+
|
461
|
+
# 2. Build query parameters
|
462
|
+
params = {'table_type': 'NPX_VOTING_TABLE'}
|
463
|
+
|
464
|
+
# Add columns parameter if provided
|
465
|
+
if columns:
|
466
|
+
if isinstance(columns, list):
|
467
|
+
params['columns'] = ','.join(columns)
|
468
|
+
else:
|
469
|
+
params['columns'] = columns
|
470
|
+
|
471
|
+
# Map Python parameter names to API parameter names
|
472
|
+
param_mapping = {
|
473
|
+
'meeting_date': 'meetingDate',
|
474
|
+
'isin': 'isin',
|
475
|
+
'cusip': 'cusip',
|
476
|
+
'issuer_name': 'issuerName',
|
477
|
+
'vote_description': 'voteDescription',
|
478
|
+
'shares_on_loan': 'sharesOnLoan',
|
479
|
+
'shares_voted': 'sharesVoted',
|
480
|
+
'vote_category': 'voteCategory',
|
481
|
+
'vote_record': 'voteRecord',
|
482
|
+
'vote_source': 'voteSource',
|
483
|
+
'how_voted': 'howVoted',
|
484
|
+
'figi': 'figi',
|
485
|
+
'management_recommendation': 'managementRecommendation',
|
486
|
+
'accession': 'accession',
|
487
|
+
'reporting_owner_cik': 'reportingOwnerCIK',
|
488
|
+
'filing_date': 'filingDate'
|
489
|
+
}
|
490
|
+
|
491
|
+
# Process all possible filter parameters
|
492
|
+
date_params = ['meeting_date', 'filing_date']
|
493
|
+
numeric_params = ['shares_on_loan', 'shares_voted']
|
494
|
+
|
495
|
+
for param_name, api_param_name in param_mapping.items():
|
496
|
+
value = locals()[param_name]
|
497
|
+
if value is not None:
|
498
|
+
# Handle different filter types
|
499
|
+
if isinstance(value, list):
|
500
|
+
# List filter
|
501
|
+
params[api_param_name] = f"[{','.join(str(v) for v in value)}]"
|
502
|
+
elif isinstance(value, tuple):
|
503
|
+
# Range filter
|
504
|
+
if len(value) == 2:
|
505
|
+
min_val, max_val = value
|
506
|
+
# Handle date range specially
|
507
|
+
if param_name in date_params:
|
508
|
+
# Dates need to be in quotes within the parentheses
|
509
|
+
if min_val is None:
|
510
|
+
min_val = ''
|
511
|
+
else:
|
512
|
+
min_val = f"'{min_val}'"
|
513
|
+
|
514
|
+
if max_val is None:
|
515
|
+
max_val = ''
|
516
|
+
else:
|
517
|
+
max_val = f"'{max_val}'"
|
518
|
+
|
519
|
+
range_str = f"({min_val},{max_val})"
|
520
|
+
params[api_param_name] = range_str
|
521
|
+
else:
|
522
|
+
raise ValueError(f"Range filter for {param_name} must be a tuple of (min, max)")
|
523
|
+
else:
|
524
|
+
# Exact match
|
525
|
+
params[api_param_name] = value
|
526
|
+
|
527
|
+
# Call common function to make API request
|
528
|
+
return _make_api_request(params, api_key, print_cost, verbose)
|