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.
@@ -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)