earningscall 1.1.0__tar.gz → 1.2.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.
- {earningscall-1.1.0 → earningscall-1.2.0}/.gitignore +4 -1
- earningscall-1.2.0/.python-version +1 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/CHANGELOG.md +10 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/PKG-INFO +20 -7
- {earningscall-1.1.0 → earningscall-1.2.0}/README.md +19 -6
- earningscall-1.2.0/earningscall/__init__.py +17 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/api.py +35 -11
- earningscall-1.2.0/earningscall/calendar.py +30 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/errors.py +4 -0
- earningscall-1.2.0/earningscall/exports.py +86 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/pyproject.toml +1 -1
- {earningscall-1.1.0 → earningscall-1.2.0}/scripts/get_all_company_transcripts.py +4 -1
- earningscall-1.2.0/scripts/get_all_sp500_transcript_texts.py +44 -0
- earningscall-1.2.0/scripts/get_calendar.py +14 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/scripts/get_single_transcript.py +2 -3
- earningscall-1.2.0/tests/data/get-calendar-500-error.yaml +14 -0
- earningscall-1.2.0/tests/data/get-calendar-not-found-response.yaml +14 -0
- earningscall-1.2.0/tests/data/get-calendar-successful-response.yaml +62 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_download_audio_files.py +0 -8
- earningscall-1.2.0/tests/test_get_calendar.py +106 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_get_transcript.py +15 -1
- earningscall-1.1.0/.python-version +0 -1
- earningscall-1.1.0/earningscall/__init__.py +0 -14
- earningscall-1.1.0/earningscall/exports.py +0 -45
- {earningscall-1.1.0 → earningscall-1.2.0}/.github/workflows/release.yml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/.github/workflows/test.yml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/DEVELOPMENT.md +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/LICENSE +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/TODO.md +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/company.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/event.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/sectors.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/symbols.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/transcript.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/earningscall/utils.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/hatch.toml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/scripts/download_audio_files.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/scripts/download_single_audio_file.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/scripts/download_sp500_audio_files.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/scripts/list_companies.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/setup.cfg +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-advanced-data-level-2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-advanced-data-level-3.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-advanced-data-level-4.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-speaker-name-map-v2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2030-not-authorized-l2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2030-not-authorized.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2030-not-found.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2030-server-error.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/demo-symbols-v2-alpha.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/demo-symbols-v2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/meta-q3-2024-not-authorized.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/meta-q3-2024-not-found.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/meta-q3-2024-other-error.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/msft-company-events.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/msft-q1-2022-audio-file-short-clip.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/msft-transcript-response.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/sp500-company-list-failed.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/sp500-company-list.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/symbols-v2-missing-edge-cases.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/symbols-v2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/symbols.txt +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/data/symbols.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_api.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_company.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_earnings_event.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_errors.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_exports.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_get_company_events.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_get_sp500_companies_api.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_helper.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_responses_mocking.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_sectors.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_symbols.py +0 -0
- {earningscall-1.1.0 → earningscall-1.2.0}/tests/test_utils.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
3.12.9
|
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Release `1.2.0` - 2025-02-12
|
4
|
+
|
5
|
+
* Add `get_calendar` function to get the calendar for a given date.
|
6
|
+
|
7
|
+
## Release `1.1.1` - 2025-01-26
|
8
|
+
|
9
|
+
* Modify default retry strategy to use 1s base delay and 10 max attempts (necessary for starter plan).
|
10
|
+
* Check for HTTP 401 Unauthorized status code from server and raise a helpful error message to the user.
|
11
|
+
* Update documentation and example scripts to reflect new retry configuration
|
12
|
+
|
3
13
|
## Release `1.1.0` - 2025-01-26
|
4
14
|
|
5
15
|
* Add backoff and retry logic to all API calls.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: earningscall
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.0
|
4
4
|
Summary: The EarningsCall Python library provides convenient access to the EarningsCall API. It includes a pre-defined set of classes for API resources that initialize themselves dynamically from API responses.
|
5
5
|
Project-URL: Homepage, https://earningscall.biz
|
6
6
|
Project-URL: Documentation, https://github.com/EarningsCall/earningscall-python
|
@@ -260,6 +260,20 @@ print("Downloading audio file for Apple Inc. Q3 2021...")
|
|
260
260
|
audio_file = company.download_audio_file(year=2021, quarter=3, file_name="Apple Q3 2021.mp3")
|
261
261
|
```
|
262
262
|
|
263
|
+
## Get Earnings Event Calendar
|
264
|
+
|
265
|
+
```python
|
266
|
+
from datetime import date
|
267
|
+
|
268
|
+
from earningscall import get_calendar
|
269
|
+
|
270
|
+
calendar = get_calendar(date(2025, 1, 10))
|
271
|
+
|
272
|
+
for event in calendar:
|
273
|
+
print(f"{event.company_name} - Q{event.quarter} {event.year} on: {event.conference_date.astimezone().isoformat()} Transcript Ready: {event.transcript_ready}")
|
274
|
+
```
|
275
|
+
|
276
|
+
|
263
277
|
## List All Companies
|
264
278
|
|
265
279
|
```python
|
@@ -297,7 +311,6 @@ for company in get_sp500_companies():
|
|
297
311
|
print(f"{company.company_info} -- {company.company_info.sector} -- {company.company_info.industry}")
|
298
312
|
```
|
299
313
|
|
300
|
-
|
301
314
|
## Advanced
|
302
315
|
|
303
316
|
### Disable Caching
|
@@ -324,8 +337,8 @@ Depending on your specific requirements, you can adjust the retry strategy. For
|
|
324
337
|
To customize the retry behavior, set the `retry_strategy` variable with the desired parameters:
|
325
338
|
|
326
339
|
- **strategy**: "exponential" | "linear" — defines the type of retry strategy (default is "exponential").
|
327
|
-
- **base_delay**: float (in seconds) — specifies the delay between retries (default is
|
328
|
-
- **max_attempts**: int — sets the maximum number of total request attempts (default is
|
340
|
+
- **base_delay**: float (in seconds) — specifies the delay between retries (default is 1 seconds).
|
341
|
+
- **max_attempts**: int — sets the maximum number of total request attempts (default is 10).
|
329
342
|
|
330
343
|
#### Default Retry Strategy
|
331
344
|
|
@@ -336,8 +349,8 @@ import earningscall
|
|
336
349
|
|
337
350
|
earningscall.retry_strategy = {
|
338
351
|
"strategy": "exponential",
|
339
|
-
"base_delay":
|
340
|
-
"max_attempts":
|
352
|
+
"base_delay": 1,
|
353
|
+
"max_attempts": 10,
|
341
354
|
}
|
342
355
|
```
|
343
356
|
|
@@ -350,7 +363,7 @@ import earningscall
|
|
350
363
|
|
351
364
|
earningscall.retry_strategy = {
|
352
365
|
"strategy": "exponential",
|
353
|
-
"base_delay":
|
366
|
+
"base_delay": 1,
|
354
367
|
"max_attempts": 1,
|
355
368
|
}
|
356
369
|
```
|
@@ -208,6 +208,20 @@ print("Downloading audio file for Apple Inc. Q3 2021...")
|
|
208
208
|
audio_file = company.download_audio_file(year=2021, quarter=3, file_name="Apple Q3 2021.mp3")
|
209
209
|
```
|
210
210
|
|
211
|
+
## Get Earnings Event Calendar
|
212
|
+
|
213
|
+
```python
|
214
|
+
from datetime import date
|
215
|
+
|
216
|
+
from earningscall import get_calendar
|
217
|
+
|
218
|
+
calendar = get_calendar(date(2025, 1, 10))
|
219
|
+
|
220
|
+
for event in calendar:
|
221
|
+
print(f"{event.company_name} - Q{event.quarter} {event.year} on: {event.conference_date.astimezone().isoformat()} Transcript Ready: {event.transcript_ready}")
|
222
|
+
```
|
223
|
+
|
224
|
+
|
211
225
|
## List All Companies
|
212
226
|
|
213
227
|
```python
|
@@ -245,7 +259,6 @@ for company in get_sp500_companies():
|
|
245
259
|
print(f"{company.company_info} -- {company.company_info.sector} -- {company.company_info.industry}")
|
246
260
|
```
|
247
261
|
|
248
|
-
|
249
262
|
## Advanced
|
250
263
|
|
251
264
|
### Disable Caching
|
@@ -272,8 +285,8 @@ Depending on your specific requirements, you can adjust the retry strategy. For
|
|
272
285
|
To customize the retry behavior, set the `retry_strategy` variable with the desired parameters:
|
273
286
|
|
274
287
|
- **strategy**: "exponential" | "linear" — defines the type of retry strategy (default is "exponential").
|
275
|
-
- **base_delay**: float (in seconds) — specifies the delay between retries (default is
|
276
|
-
- **max_attempts**: int — sets the maximum number of total request attempts (default is
|
288
|
+
- **base_delay**: float (in seconds) — specifies the delay between retries (default is 1 seconds).
|
289
|
+
- **max_attempts**: int — sets the maximum number of total request attempts (default is 10).
|
277
290
|
|
278
291
|
#### Default Retry Strategy
|
279
292
|
|
@@ -284,8 +297,8 @@ import earningscall
|
|
284
297
|
|
285
298
|
earningscall.retry_strategy = {
|
286
299
|
"strategy": "exponential",
|
287
|
-
"base_delay":
|
288
|
-
"max_attempts":
|
300
|
+
"base_delay": 1,
|
301
|
+
"max_attempts": 10,
|
289
302
|
}
|
290
303
|
```
|
291
304
|
|
@@ -298,7 +311,7 @@ import earningscall
|
|
298
311
|
|
299
312
|
earningscall.retry_strategy = {
|
300
313
|
"strategy": "exponential",
|
301
|
-
"base_delay":
|
314
|
+
"base_delay": 1,
|
302
315
|
"max_attempts": 1,
|
303
316
|
}
|
304
317
|
```
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from typing import Dict, Optional, Union
|
2
|
+
|
3
|
+
from earningscall.exports import get_company, get_all_companies, get_sp500_companies, get_calendar
|
4
|
+
from earningscall.symbols import Symbols, load_symbols
|
5
|
+
|
6
|
+
api_key: Optional[str] = None
|
7
|
+
enable_requests_cache: bool = True
|
8
|
+
retry_strategy: Optional[Dict[str, Union[str, int, float]]] = None
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"get_company",
|
12
|
+
"get_all_companies",
|
13
|
+
"get_sp500_companies",
|
14
|
+
"Symbols",
|
15
|
+
"load_symbols",
|
16
|
+
"get_calendar",
|
17
|
+
]
|
@@ -1,21 +1,27 @@
|
|
1
1
|
import importlib
|
2
|
-
import platform
|
3
|
-
import urllib.parse
|
4
2
|
import logging
|
5
3
|
import os
|
4
|
+
import platform
|
6
5
|
import time
|
6
|
+
import urllib.parse
|
7
7
|
from importlib.metadata import PackageNotFoundError
|
8
|
-
from typing import Optional
|
8
|
+
from typing import Dict, Optional, Union
|
9
9
|
|
10
10
|
import requests
|
11
11
|
from requests_cache import CachedSession
|
12
12
|
|
13
13
|
import earningscall
|
14
|
+
from earningscall.errors import InvalidApiKeyError
|
14
15
|
|
15
16
|
log = logging.getLogger(__file__)
|
16
17
|
|
17
|
-
DOMAIN = os.environ.get("
|
18
|
+
DOMAIN = os.environ.get("EARNINGSCALL_DOMAIN", "earningscall.biz")
|
18
19
|
API_BASE = f"https://v2.api.{DOMAIN}"
|
20
|
+
DEFAULT_RETRY_STRATEGY: Dict[str, Union[str, int, float]] = {
|
21
|
+
"strategy": "exponential",
|
22
|
+
"base_delay": 1,
|
23
|
+
"max_attempts": 10,
|
24
|
+
}
|
19
25
|
|
20
26
|
|
21
27
|
def get_api_key():
|
@@ -121,8 +127,9 @@ def do_get(
|
|
121
127
|
full_url = f"{url}?{urllib.parse.urlencode(params)}"
|
122
128
|
log.debug(f"GET: {full_url}")
|
123
129
|
|
124
|
-
|
125
|
-
|
130
|
+
retry_strategy = earningscall.retry_strategy or DEFAULT_RETRY_STRATEGY
|
131
|
+
delay = retry_strategy["base_delay"]
|
132
|
+
max_attempts = int(retry_strategy["max_attempts"])
|
126
133
|
|
127
134
|
for attempt in range(max_attempts):
|
128
135
|
if use_cache and earningscall.enable_requests_cache:
|
@@ -138,16 +145,22 @@ def do_get(
|
|
138
145
|
if is_success(response):
|
139
146
|
return response
|
140
147
|
|
148
|
+
if response.status_code == 401:
|
149
|
+
raise InvalidApiKeyError(
|
150
|
+
"Your API key is invalid. You can get your API key at: https://earningscall.biz/api-key"
|
151
|
+
)
|
152
|
+
|
141
153
|
if not can_retry(response):
|
142
154
|
return response
|
143
155
|
|
144
156
|
if attempt < max_attempts - 1: # Don't sleep after the last attempt
|
145
|
-
if
|
146
|
-
wait_time = delay * (2**attempt) # Exponential backoff:
|
147
|
-
elif
|
148
|
-
wait_time = delay * (attempt + 1) # Linear backoff: 3s ->
|
157
|
+
if retry_strategy["strategy"] == "exponential":
|
158
|
+
wait_time = delay * (2**attempt) # Exponential backoff: 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s
|
159
|
+
elif retry_strategy["strategy"] == "linear":
|
160
|
+
wait_time = delay * (attempt + 1) # Linear backoff: 1s -> 2s -> 3s -> 4s -> 5s -> 6s -> 7s
|
149
161
|
else:
|
150
162
|
raise ValueError("Invalid retry strategy. Must be one of: 'exponential', 'linear'")
|
163
|
+
# TODO: Should we log a warning here? Does the customer want to see this log?
|
151
164
|
log.warning(
|
152
165
|
f"Rate limited (429). Retrying in {wait_time} seconds... (Attempt {attempt + 1}/{max_attempts})"
|
153
166
|
)
|
@@ -156,7 +169,18 @@ def do_get(
|
|
156
169
|
return response # Return the last response if all retries failed
|
157
170
|
|
158
171
|
|
159
|
-
def
|
172
|
+
def get_calendar_api_operation(year: int, month: int, day: int) -> dict:
|
173
|
+
params = {
|
174
|
+
"year": str(year),
|
175
|
+
"month": str(month),
|
176
|
+
"day": str(day),
|
177
|
+
}
|
178
|
+
response = do_get("calendar", params=params)
|
179
|
+
response.raise_for_status()
|
180
|
+
return response.json()
|
181
|
+
|
182
|
+
|
183
|
+
def get_events(exchange: str, symbol: str) -> Optional[dict]:
|
160
184
|
params = {
|
161
185
|
"exchange": exchange,
|
162
186
|
"symbol": symbol,
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from dataclasses_json import config
|
6
|
+
from dataclasses_json import dataclass_json
|
7
|
+
from marshmallow import fields
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass_json
|
11
|
+
@dataclass
|
12
|
+
class CalendarEvent:
|
13
|
+
"""
|
14
|
+
CalendarEvent
|
15
|
+
"""
|
16
|
+
|
17
|
+
company_name: str
|
18
|
+
exchange: str
|
19
|
+
symbol: str
|
20
|
+
year: int
|
21
|
+
quarter: int
|
22
|
+
transcript_ready: bool
|
23
|
+
conference_date: Optional[datetime] = field(
|
24
|
+
default=None,
|
25
|
+
metadata=config(
|
26
|
+
encoder=lambda date: date.isoformat() if date else None,
|
27
|
+
decoder=lambda date: datetime.fromisoformat(date) if date else None,
|
28
|
+
mm_field=fields.DateTime(format="iso"),
|
29
|
+
),
|
30
|
+
)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import datetime
|
2
|
+
from datetime import date, timedelta
|
3
|
+
from typing import List, Optional, Iterator
|
4
|
+
|
5
|
+
import requests
|
6
|
+
|
7
|
+
from earningscall import api
|
8
|
+
from earningscall.api import get_sp500_companies_txt_file, is_demo_account
|
9
|
+
from earningscall.calendar import CalendarEvent
|
10
|
+
from earningscall.company import Company
|
11
|
+
from earningscall.errors import InsufficientApiAccessError
|
12
|
+
from earningscall.symbols import get_symbols
|
13
|
+
|
14
|
+
|
15
|
+
def get_company(symbol: str, exchange: Optional[str] = None) -> Optional[Company]:
|
16
|
+
"""
|
17
|
+
Get a company by symbol and optionally an exchange.
|
18
|
+
|
19
|
+
:param str symbol: The symbol to get the company for.
|
20
|
+
:param Optional[str] exchange: The exchange to get the company for.
|
21
|
+
|
22
|
+
:return: The company for the given symbol and exchange.
|
23
|
+
"""
|
24
|
+
company_info = get_symbols().lookup_company(symbol=symbol, exchange=exchange)
|
25
|
+
if company_info:
|
26
|
+
return Company(company_info=company_info)
|
27
|
+
return None
|
28
|
+
|
29
|
+
|
30
|
+
def get_all_companies() -> Iterator[Company]:
|
31
|
+
"""
|
32
|
+
Get all companies.
|
33
|
+
|
34
|
+
:return: An iterator of all companies that is available to the current API plan.
|
35
|
+
"""
|
36
|
+
for company_info in get_symbols().get_all():
|
37
|
+
yield Company(company_info=company_info)
|
38
|
+
|
39
|
+
|
40
|
+
def get_sp500_companies() -> Iterator[Company]:
|
41
|
+
"""
|
42
|
+
Get all S&P 500 companies.
|
43
|
+
|
44
|
+
:return: An iterator of all S&P 500 companies that is available to the current API plan.
|
45
|
+
"""
|
46
|
+
resp = get_sp500_companies_txt_file()
|
47
|
+
if not resp:
|
48
|
+
return
|
49
|
+
for ticker_symbol in resp.split("\n"):
|
50
|
+
company_info = get_symbols().lookup_company(ticker_symbol)
|
51
|
+
if company_info:
|
52
|
+
yield Company(company_info=company_info)
|
53
|
+
|
54
|
+
|
55
|
+
def get_calendar(input_date: date) -> List[CalendarEvent]:
|
56
|
+
"""
|
57
|
+
Get the earnings event calendar for a given input date.
|
58
|
+
|
59
|
+
:param date input_date: The date to get the calendar for.
|
60
|
+
|
61
|
+
:return: A list of CalendarEvent objects.
|
62
|
+
"""
|
63
|
+
if not input_date:
|
64
|
+
raise ValueError("Date is required")
|
65
|
+
if isinstance(input_date, datetime.datetime):
|
66
|
+
input_date = input_date.date()
|
67
|
+
if not isinstance(input_date, date):
|
68
|
+
raise ValueError("Date must be a date object")
|
69
|
+
if input_date < date(2018, 1, 1):
|
70
|
+
raise ValueError("input_date must be greater than or equal to 2018-01-01")
|
71
|
+
# Check if input_date is greater than 30 days from today
|
72
|
+
if input_date > datetime.datetime.now().date() + timedelta(days=30):
|
73
|
+
raise ValueError("input_date must be less than 30 days from today")
|
74
|
+
if is_demo_account() and input_date != date(2025, 1, 10):
|
75
|
+
raise InsufficientApiAccessError(
|
76
|
+
f"\"{input_date}\" requires an API Key for access. To get your API Key,"
|
77
|
+
f" see: https://earningscall.biz/api-pricing"
|
78
|
+
)
|
79
|
+
try:
|
80
|
+
json_data = api.get_calendar_api_operation(input_date.year, input_date.month, input_date.day)
|
81
|
+
return [CalendarEvent.from_dict(event) for event in json_data] # type: ignore
|
82
|
+
except requests.exceptions.HTTPError as error:
|
83
|
+
if error.response.status_code == 404:
|
84
|
+
# Calendar Date not found, simply return an empty list
|
85
|
+
return []
|
86
|
+
raise error
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "earningscall"
|
3
|
-
version = "1.
|
3
|
+
version = "1.2.0"
|
4
4
|
description = "The EarningsCall Python library provides convenient access to the EarningsCall API. It includes a pre-defined set of classes for API resources that initialize themselves dynamically from API responses."
|
5
5
|
readme = "README.md"
|
6
6
|
authors = [{ name = "EarningsCall", email = "dev@earningscall.biz" }]
|
@@ -1,7 +1,10 @@
|
|
1
|
+
import earningscall # noqa: F401
|
1
2
|
from earningscall import get_company
|
2
3
|
|
4
|
+
# earningscall.api_key = "YOUR API KEY HERE"
|
3
5
|
|
4
|
-
|
6
|
+
|
7
|
+
company = get_company("AAPL") # Lookup Apple, Inc by its ticker symbol, "AAPL"
|
5
8
|
|
6
9
|
print(f"Getting all transcripts for: {company}..")
|
7
10
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
import os
|
3
|
+
|
4
|
+
import earningscall # noqa: F401
|
5
|
+
from earningscall import get_sp500_companies
|
6
|
+
from earningscall.company import Company
|
7
|
+
|
8
|
+
# TODO: Set your API key here:
|
9
|
+
# earningscall.api_key = "YOUR SECRET API KEY GOES HERE"
|
10
|
+
|
11
|
+
directory = "data/transcript_texts"
|
12
|
+
os.makedirs(directory, exist_ok=True)
|
13
|
+
|
14
|
+
|
15
|
+
def download_transcript_texts(company: Company):
|
16
|
+
print(f"Downloading all transcript texts for: {company}..")
|
17
|
+
for event in company.events():
|
18
|
+
if datetime.now().timestamp() < event.conference_date.timestamp():
|
19
|
+
print(
|
20
|
+
f"* {company.company_info.symbol} Q{event.quarter} {event.year} -- skipping, conference date in the future"
|
21
|
+
)
|
22
|
+
continue
|
23
|
+
file_name = os.path.join(
|
24
|
+
directory,
|
25
|
+
f"{company.company_info.exchange}_{company.company_info.symbol}_{event.year}_Q{event.quarter}.text",
|
26
|
+
)
|
27
|
+
if os.path.exists(file_name):
|
28
|
+
print(f"* {company.company_info.symbol} Q{event.quarter} {event.year} -- already downloaded")
|
29
|
+
else:
|
30
|
+
print(f"* Downloading transcript text for {company.company_info.symbol} Q{event.quarter} {event.year}...")
|
31
|
+
transcript = company.get_transcript(event=event)
|
32
|
+
if transcript:
|
33
|
+
# Save transcript text to file
|
34
|
+
with open(file_name, "w") as fd:
|
35
|
+
fd.write(transcript.text)
|
36
|
+
print(
|
37
|
+
f" Downloaded transcript text for {company.company_info.symbol} Q{event.quarter} {event.year} to: {file_name}"
|
38
|
+
)
|
39
|
+
else:
|
40
|
+
print(f" No transcript text found for {company.company_info.symbol} Q{event.quarter} {event.year}")
|
41
|
+
|
42
|
+
|
43
|
+
for company in get_sp500_companies():
|
44
|
+
download_transcript_texts(company)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from datetime import date
|
2
|
+
import earningscall # noqa: F401
|
3
|
+
|
4
|
+
from earningscall import get_calendar
|
5
|
+
|
6
|
+
# TODO: Set your API key here:
|
7
|
+
# earningscall.api_key = "YOUR API KEY HERE"
|
8
|
+
|
9
|
+
events = get_calendar(date(2025, 1, 10))
|
10
|
+
|
11
|
+
for event in events:
|
12
|
+
print(
|
13
|
+
f"{event.company_name} - Q{event.quarter} {event.year} on: {event.conference_date.astimezone().isoformat()} Transcript Ready: {event.transcript_ready}"
|
14
|
+
)
|
@@ -4,10 +4,9 @@ from earningscall import get_company
|
|
4
4
|
|
5
5
|
|
6
6
|
# TODO: Set your API key here:
|
7
|
-
# earningscall.api_key = "YOUR
|
7
|
+
# earningscall.api_key = "YOUR API KEY HERE"
|
8
8
|
|
9
|
-
|
10
|
-
company = get_company("aapl")
|
9
|
+
company = get_company("AAPL")
|
11
10
|
|
12
11
|
transcript = company.get_transcript(year=2021, quarter=3, level=2)
|
13
12
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
responses:
|
2
|
+
- response:
|
3
|
+
auto_calculate_content_length: false
|
4
|
+
body: ''
|
5
|
+
content_type: text/plain
|
6
|
+
headers:
|
7
|
+
Via: 1.1 1cae0bb0106fc058447f3b32dee7b228.cloudfront.net (CloudFront)
|
8
|
+
X-Amz-Cf-Id: GJG7c5roSXmd3WXEWTNzyrl8VN9fWmIm3OR8oYxEZg8bWpoiZE6XvA==
|
9
|
+
X-Amz-Cf-Pop: IAH50-C4
|
10
|
+
X-Cache: FunctionGeneratedResponse from cloudfront
|
11
|
+
X-Plan-Name: demo
|
12
|
+
method: GET
|
13
|
+
status: 500
|
14
|
+
url: https://v2.api.earningscall.biz/calendar?apikey=XXXXXXXXXXX&year=2020&month=1&day=1
|
@@ -0,0 +1,14 @@
|
|
1
|
+
responses:
|
2
|
+
- response:
|
3
|
+
auto_calculate_content_length: false
|
4
|
+
body: ''
|
5
|
+
content_type: text/plain
|
6
|
+
headers:
|
7
|
+
Via: 1.1 1cae0bb0106fc058447f3b32dee7b228.cloudfront.net (CloudFront)
|
8
|
+
X-Amz-Cf-Id: GJG7c5roSXmd3WXEWTNzyrl8VN9fWmIm3OR8oYxEZg8bWpoiZE6XvA==
|
9
|
+
X-Amz-Cf-Pop: IAH50-C4
|
10
|
+
X-Cache: FunctionGeneratedResponse from cloudfront
|
11
|
+
X-Plan-Name: starter
|
12
|
+
method: GET
|
13
|
+
status: 404
|
14
|
+
url: https://v2.api.earningscall.biz/calendar?apikey=XXXXXXXXXXX&year=2018&month=1&day=1
|
@@ -0,0 +1,62 @@
|
|
1
|
+
responses:
|
2
|
+
- response:
|
3
|
+
auto_calculate_content_length: false
|
4
|
+
body: '[{"exchange": "NASDAQ", "symbol": "MPAA", "year": 2025, "quarter": 3, "conference_date":
|
5
|
+
"2025-02-10T13:00:00.000-05:00", "company_name": "Motorcar Parts of America,
|
6
|
+
Inc.", "transcript_ready": true}, {"exchange": "NASDAQ", "symbol": "SPSC", "year":
|
7
|
+
2024, "quarter": 4, "conference_date": "2025-02-10T16:30:00-05:00", "company_name":
|
8
|
+
"SPS Commerce, Inc.", "transcript_ready": true}, {"exchange": "NASDAQ", "symbol":
|
9
|
+
"RICK", "year": 2025, "quarter": 1, "conference_date": "2025-02-10T16:30:00-05:00",
|
10
|
+
"company_name": "RCI Hospitality Holdings, Inc.", "transcript_ready": true},
|
11
|
+
{"exchange": "NASDAQ", "symbol": "ALAB", "year": 2024, "quarter": 4, "conference_date":
|
12
|
+
"2025-02-10T16:30:00-05:00", "company_name": "Astera Labs, Inc.", "transcript_ready":
|
13
|
+
true}, {"exchange": "NASDAQ", "symbol": "PETS", "year": 2025, "quarter": 3,
|
14
|
+
"conference_date": "2025-02-10T16:30:00-05:00", "company_name": "PetMed Express,
|
15
|
+
Inc.", "transcript_ready": true}, {"exchange": "NASDAQ", "symbol": "XAIR", "year":
|
16
|
+
2025, "quarter": 3, "conference_date": "2025-02-10T16:30:00-05:00", "company_name":
|
17
|
+
"Beyond Air, Inc.", "transcript_ready": true}, {"exchange": "NASDAQ", "symbol":
|
18
|
+
"BLFS", "year": 2024, "quarter": 4, "conference_date": "2025-02-10T16:30:00.000-05:00",
|
19
|
+
"company_name": "BioLife Solutions, Inc.", "transcript_ready": true}, {"exchange":
|
20
|
+
"NASDAQ", "symbol": "VRTX", "year": 2024, "quarter": 4, "conference_date": "2025-02-10T16:30:00.000-05:00",
|
21
|
+
"company_name": "Vertex Pharmaceuticals Incorporated", "transcript_ready": true},
|
22
|
+
{"exchange": "NYSE", "symbol": "TAK", "year": 2025, "quarter": 1, "conference_date":
|
23
|
+
"2025-02-10T16:30:00-05:00", "company_name": "Takeda Pharmaceutical Company
|
24
|
+
Limited", "transcript_ready": true}, {"exchange": "OTC", "symbol": "PRKA", "year":
|
25
|
+
2025, "quarter": 1, "conference_date": "2025-02-10T16:30:00.000-05:00", "company_name":
|
26
|
+
"Parks! America Inc", "transcript_ready": true}, {"exchange": "NASDAQ", "symbol":
|
27
|
+
"AMKR", "year": 2024, "quarter": 4, "conference_date": "2025-02-10T17:00:00.000-05:00",
|
28
|
+
"company_name": "Amkor Technology, Inc.", "transcript_ready": true}, {"exchange":
|
29
|
+
"NASDAQ", "symbol": "MITK", "year": 2025, "quarter": 1, "conference_date": "2025-02-10T17:00:00-05:00",
|
30
|
+
"company_name": "Mitek Systems, Inc.", "transcript_ready": true}, {"exchange":
|
31
|
+
"NYSE", "symbol": "SLQT", "year": 2025, "quarter": 2, "conference_date": "2025-02-10T17:00:00-05:00",
|
32
|
+
"company_name": "SelectQuote, Inc.", "transcript_ready": true}, {"exchange":
|
33
|
+
"NYSE", "symbol": "NGL", "year": 2025, "quarter": 3, "conference_date": "2025-02-10T16:00:00-06:00",
|
34
|
+
"company_name": "NGL Energy Partners LP", "transcript_ready": true}, {"exchange":
|
35
|
+
"NASDAQ", "symbol": "LSCC", "year": 2024, "quarter": 4, "conference_date": "2025-02-10T17:00:00-05:00",
|
36
|
+
"company_name": "Lattice Semiconductor Corporation", "transcript_ready": true},
|
37
|
+
{"exchange": "TSX", "symbol": "CVO", "year": 2025, "quarter": 3, "conference_date":
|
38
|
+
"2025-02-10T17:00:00-05:00", "company_name": "Coveo Solutions Inc.", "transcript_ready":
|
39
|
+
true}, {"exchange": "NYSE", "symbol": "INSP", "year": 2024, "quarter": 4, "conference_date":
|
40
|
+
"2025-02-10T17:00:00-05:00", "company_name": "Inspire Medical Systems, Inc.",
|
41
|
+
"transcript_ready": true}, {"exchange": "NASDAQ", "symbol": "CMCO", "year":
|
42
|
+
2025, "quarter": 3, "conference_date": "2025-02-10T17:00:00-05:00", "company_name":
|
43
|
+
"Columbus McKinnon Corporation", "transcript_ready": true}, {"exchange": "NASDAQ",
|
44
|
+
"symbol": "HLIT", "year": 2024, "quarter": 4, "conference_date": "2025-02-10T14:00:00-08:00",
|
45
|
+
"company_name": "Harmonic Inc.", "transcript_ready": true}, {"exchange": "NYSE",
|
46
|
+
"symbol": "SSD", "year": 2024, "quarter": 4, "conference_date": "2025-02-10T17:00:00-05:00",
|
47
|
+
"company_name": "Simpson Manufacturing Company, Inc.", "transcript_ready": true}]'
|
48
|
+
content_type: text/plain
|
49
|
+
headers:
|
50
|
+
Cache-Control: public, max-age=500
|
51
|
+
ETag: W/"ff349ae06e2284df57d342fd9e3c9d4b"
|
52
|
+
Last-Modified: Wed, 12 Feb 2025 15:44:12 GMT
|
53
|
+
Transfer-Encoding: chunked
|
54
|
+
Vary: accept-encoding
|
55
|
+
Via: 1.1 dab9621fb9e60d4beae799f308450f86.cloudfront.net (CloudFront)
|
56
|
+
X-Amz-Cf-Id: o-89nbe8YAinJi-F0rXkAr8qIUAcTkW6Mmvk4vQeDH-9ob1MZ7QVfg==
|
57
|
+
X-Amz-Cf-Pop: IAH50-C4
|
58
|
+
X-Cache: Miss from cloudfront
|
59
|
+
x-amz-server-side-encryption: AES256
|
60
|
+
method: GET
|
61
|
+
status: 200
|
62
|
+
url: https://v2.api.earningscall.biz/calendar?apikey=XXXXXXXXXXX&year=2025&month=2&day=10
|
@@ -14,14 +14,6 @@ from earningscall.symbols import CompanyInfo, clear_symbols
|
|
14
14
|
from earningscall.utils import data_path
|
15
15
|
|
16
16
|
|
17
|
-
# Uncomment and run following code to generate msft-transcript-response.yaml file
|
18
|
-
#
|
19
|
-
# from responses import _recorder
|
20
|
-
# @_recorder.record(file_path="meta-q3-2024-not-found.yaml")
|
21
|
-
# def test_save_symbols_v1():
|
22
|
-
# requests.get("https://v2.api.alpha.earningscall.biz/audio?apikey=brocktest&exchange=NASDAQ&symbol=META&year=2024&quarter=3")
|
23
|
-
|
24
|
-
|
25
17
|
@pytest.fixture(autouse=True)
|
26
18
|
def run_before_and_after_tests():
|
27
19
|
"""Fixture to execute asserts before and after a test is run"""
|
@@ -0,0 +1,106 @@
|
|
1
|
+
from datetime import datetime, date, timedelta
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
import requests
|
5
|
+
import responses
|
6
|
+
|
7
|
+
import earningscall
|
8
|
+
from earningscall import get_calendar
|
9
|
+
from earningscall.api import purge_cache
|
10
|
+
from earningscall.errors import InsufficientApiAccessError
|
11
|
+
from earningscall.utils import data_path
|
12
|
+
|
13
|
+
|
14
|
+
# Uncomment and run following code to generate data/get-calendar-not-found-response.yaml file
|
15
|
+
#
|
16
|
+
|
17
|
+
# import earningscall
|
18
|
+
# @_recorder.record(file_path="data/get-calendar-not-found-response.yaml")
|
19
|
+
# def test_save_symbols_v1():
|
20
|
+
# requests.get("https://v2.api.earningscall.biz/calendar?apikey=demo&year=1980&month=1&day=1")
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.fixture(autouse=True)
|
24
|
+
def run_before_and_after_tests():
|
25
|
+
"""Fixture to execute asserts before and after a test is run"""
|
26
|
+
# Setup
|
27
|
+
earningscall.api_key = None
|
28
|
+
earningscall.retry_strategy = {
|
29
|
+
"strategy": "exponential",
|
30
|
+
"base_delay": 0.01,
|
31
|
+
"max_attempts": 3,
|
32
|
+
}
|
33
|
+
purge_cache()
|
34
|
+
yield # this is where the testing happens
|
35
|
+
# Teardown
|
36
|
+
earningscall.api_key = None
|
37
|
+
|
38
|
+
|
39
|
+
@responses.activate
|
40
|
+
def test_get_calendar_invalid_inputs():
|
41
|
+
with pytest.raises(ValueError):
|
42
|
+
get_calendar(None)
|
43
|
+
with pytest.raises(ValueError):
|
44
|
+
get_calendar("2025-02-10")
|
45
|
+
with pytest.raises(ValueError):
|
46
|
+
get_calendar(123456)
|
47
|
+
with pytest.raises(ValueError):
|
48
|
+
get_calendar(date(2017, 12, 31))
|
49
|
+
with pytest.raises(ValueError):
|
50
|
+
get_calendar(datetime.now() + timedelta(days=31))
|
51
|
+
|
52
|
+
|
53
|
+
@responses.activate
|
54
|
+
def test_get_non_demo_date():
|
55
|
+
with pytest.raises(InsufficientApiAccessError):
|
56
|
+
get_calendar(date(2020, 1, 1))
|
57
|
+
|
58
|
+
|
59
|
+
@responses.activate
|
60
|
+
def test_get_calendar_success():
|
61
|
+
earningscall.api_key = "XXXXXXXXXXX"
|
62
|
+
responses._add_from_file(file_path=data_path("get-calendar-successful-response.yaml"))
|
63
|
+
calendar = get_calendar(date(2025, 2, 10))
|
64
|
+
assert len(calendar) == 20
|
65
|
+
assert calendar[0].exchange == "NASDAQ"
|
66
|
+
assert calendar[0].symbol == "MPAA"
|
67
|
+
assert calendar[0].year == 2025
|
68
|
+
assert calendar[0].quarter == 3
|
69
|
+
assert calendar[0].conference_date.year == 2025
|
70
|
+
assert calendar[0].conference_date.month == 2
|
71
|
+
assert calendar[0].conference_date.day == 10
|
72
|
+
assert calendar[0].transcript_ready
|
73
|
+
assert calendar[0].company_name == "Motorcar Parts of America, Inc."
|
74
|
+
|
75
|
+
|
76
|
+
@responses.activate
|
77
|
+
def test_get_calendar_success_with_datetime():
|
78
|
+
earningscall.api_key = "XXXXXXXXXXX"
|
79
|
+
responses._add_from_file(file_path=data_path("get-calendar-successful-response.yaml"))
|
80
|
+
calendar = get_calendar(datetime(2025, 2, 10))
|
81
|
+
assert len(calendar) == 20
|
82
|
+
assert calendar[0].exchange == "NASDAQ"
|
83
|
+
assert calendar[0].symbol == "MPAA"
|
84
|
+
assert calendar[0].year == 2025
|
85
|
+
assert calendar[0].quarter == 3
|
86
|
+
assert calendar[0].conference_date.year == 2025
|
87
|
+
assert calendar[0].conference_date.month == 2
|
88
|
+
assert calendar[0].conference_date.day == 10
|
89
|
+
assert calendar[0].transcript_ready
|
90
|
+
assert calendar[0].company_name == "Motorcar Parts of America, Inc."
|
91
|
+
|
92
|
+
|
93
|
+
@responses.activate
|
94
|
+
def test_get_calendar_server_error():
|
95
|
+
earningscall.api_key = "XXXXXXXXXXX"
|
96
|
+
responses._add_from_file(file_path=data_path("get-calendar-500-error.yaml"))
|
97
|
+
with pytest.raises(requests.exceptions.HTTPError):
|
98
|
+
get_calendar(date(2020, 1, 1))
|
99
|
+
|
100
|
+
|
101
|
+
@responses.activate
|
102
|
+
def test_get_calendar_not_found():
|
103
|
+
earningscall.api_key = "XXXXXXXXXXX"
|
104
|
+
responses._add_from_file(file_path=data_path("get-calendar-not-found-response.yaml"))
|
105
|
+
calendar = get_calendar(date(2018, 1, 1))
|
106
|
+
assert len(calendar) == 0
|
@@ -8,7 +8,7 @@ import earningscall
|
|
8
8
|
from earningscall import get_company
|
9
9
|
from earningscall.api import purge_cache
|
10
10
|
from earningscall.company import Company
|
11
|
-
from earningscall.errors import InsufficientApiAccessError
|
11
|
+
from earningscall.errors import InsufficientApiAccessError, InvalidApiKeyError
|
12
12
|
from earningscall.event import EarningsEvent
|
13
13
|
from earningscall.symbols import clear_symbols, CompanyInfo
|
14
14
|
from earningscall.transcript import Transcript
|
@@ -372,6 +372,20 @@ def test_get_transcript_fails_all_attempts_invalid_retry_strategy():
|
|
372
372
|
company.get_transcript(year=2023, quarter=1, level=1)
|
373
373
|
|
374
374
|
|
375
|
+
@responses.activate
|
376
|
+
def test_get_company_fails_not_authorized():
|
377
|
+
# Always throttle the caller
|
378
|
+
responses.add(
|
379
|
+
responses.GET,
|
380
|
+
"https://v2.api.earningscall.biz/symbols-v2.txt",
|
381
|
+
body=json.dumps({"error": "Not authorized"}),
|
382
|
+
status=401,
|
383
|
+
)
|
384
|
+
##
|
385
|
+
with pytest.raises(InvalidApiKeyError):
|
386
|
+
get_company("aapl")
|
387
|
+
|
388
|
+
|
375
389
|
# Uncomment and run following code to generate demo-symbols-v2.yaml file
|
376
390
|
#
|
377
391
|
# import requests
|
@@ -1 +0,0 @@
|
|
1
|
-
3.12.8
|
@@ -1,14 +0,0 @@
|
|
1
|
-
from typing import Dict, Optional, Union
|
2
|
-
|
3
|
-
from earningscall.exports import get_company, get_all_companies, get_sp500_companies
|
4
|
-
from earningscall.symbols import Symbols, load_symbols
|
5
|
-
|
6
|
-
api_key: Optional[str] = None
|
7
|
-
enable_requests_cache: bool = True
|
8
|
-
retry_strategy: Dict[str, Union[str, int, float]] = {
|
9
|
-
"strategy": "exponential",
|
10
|
-
"base_delay": 3,
|
11
|
-
"max_attempts": 5,
|
12
|
-
}
|
13
|
-
|
14
|
-
__all__ = ["get_company", "get_all_companies", "get_sp500_companies", "Symbols", "load_symbols"]
|
@@ -1,45 +0,0 @@
|
|
1
|
-
from typing import Optional, Iterator
|
2
|
-
|
3
|
-
from earningscall.api import get_sp500_companies_txt_file
|
4
|
-
from earningscall.company import Company
|
5
|
-
from earningscall.symbols import get_symbols
|
6
|
-
|
7
|
-
|
8
|
-
def get_company(symbol: str, exchange: Optional[str] = None) -> Optional[Company]:
|
9
|
-
"""
|
10
|
-
Get a company by symbol and optionally an exchange.
|
11
|
-
|
12
|
-
:param str symbol: The symbol to get the company for.
|
13
|
-
:param Optional[str] exchange: The exchange to get the company for.
|
14
|
-
|
15
|
-
:return: The company for the given symbol and exchange.
|
16
|
-
"""
|
17
|
-
company_info = get_symbols().lookup_company(symbol=symbol, exchange=exchange)
|
18
|
-
if company_info:
|
19
|
-
return Company(company_info=company_info)
|
20
|
-
return None
|
21
|
-
|
22
|
-
|
23
|
-
def get_all_companies() -> Iterator[Company]:
|
24
|
-
"""
|
25
|
-
Get all companies.
|
26
|
-
|
27
|
-
:return: An iterator of all companies that is available to the current API plan.
|
28
|
-
"""
|
29
|
-
for company_info in get_symbols().get_all():
|
30
|
-
yield Company(company_info=company_info)
|
31
|
-
|
32
|
-
|
33
|
-
def get_sp500_companies() -> Iterator[Company]:
|
34
|
-
"""
|
35
|
-
Get all S&P 500 companies.
|
36
|
-
|
37
|
-
:return: An iterator of all S&P 500 companies that is available to the current API plan.
|
38
|
-
"""
|
39
|
-
resp = get_sp500_companies_txt_file()
|
40
|
-
if not resp:
|
41
|
-
return
|
42
|
-
for ticker_symbol in resp.split("\n"):
|
43
|
-
company_info = get_symbols().lookup_company(ticker_symbol)
|
44
|
-
if company_info:
|
45
|
-
yield Company(company_info=company_info)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-advanced-data-level-2.yaml
RENAMED
File without changes
|
{earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-advanced-data-level-3.yaml
RENAMED
File without changes
|
{earningscall-1.1.0 → earningscall-1.2.0}/tests/data/aapl-q1-2022-advanced-data-level-4.yaml
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{earningscall-1.1.0 → earningscall-1.2.0}/tests/data/msft-q1-2022-audio-file-short-clip.yaml
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|