earningscall 1.1.0__tar.gz → 1.1.1__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.1.1}/.gitignore +4 -1
- {earningscall-1.1.0 → earningscall-1.1.1}/CHANGELOG.md +6 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/PKG-INFO +6 -6
- {earningscall-1.1.0 → earningscall-1.1.1}/README.md +5 -5
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/__init__.py +1 -5
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/api.py +21 -8
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/errors.py +4 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/pyproject.toml +1 -1
- {earningscall-1.1.0 → earningscall-1.1.1}/scripts/get_all_company_transcripts.py +4 -1
- earningscall-1.1.1/scripts/get_all_sp500_transcript_texts.py +44 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/scripts/get_single_transcript.py +2 -3
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_get_transcript.py +15 -1
- {earningscall-1.1.0 → earningscall-1.1.1}/.github/workflows/release.yml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/.github/workflows/test.yml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/.python-version +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/DEVELOPMENT.md +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/LICENSE +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/TODO.md +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/company.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/event.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/exports.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/sectors.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/symbols.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/transcript.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/earningscall/utils.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/hatch.toml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/scripts/download_audio_files.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/scripts/download_single_audio_file.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/scripts/download_sp500_audio_files.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/scripts/list_companies.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/setup.cfg +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2022-advanced-data-level-2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2022-advanced-data-level-3.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2022-advanced-data-level-4.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2022-speaker-name-map-v2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2030-not-authorized-l2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2030-not-authorized.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2030-not-found.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2030-server-error.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/demo-symbols-v2-alpha.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/demo-symbols-v2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/meta-q3-2024-not-authorized.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/meta-q3-2024-not-found.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/meta-q3-2024-other-error.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/msft-company-events.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/msft-q1-2022-audio-file-short-clip.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/msft-transcript-response.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/sp500-company-list-failed.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/sp500-company-list.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/symbols-v2-missing-edge-cases.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/symbols-v2.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/symbols.txt +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/data/symbols.yaml +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_api.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_company.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_download_audio_files.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_earnings_event.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_errors.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_exports.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_get_company_events.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_get_sp500_companies_api.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_helper.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_responses_mocking.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_sectors.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_symbols.py +0 -0
- {earningscall-1.1.0 → earningscall-1.1.1}/tests/test_utils.py +0 -0
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Release `1.1.1` - 2025-01-26
|
4
|
+
|
5
|
+
* Modify default retry strategy to use 1s base delay and 10 max attempts (necessary for starter plan).
|
6
|
+
* Check for HTTP 401 Unauthorized status code from server and raise a helpful error message to the user.
|
7
|
+
* Update documentation and example scripts to reflect new retry configuration
|
8
|
+
|
3
9
|
## Release `1.1.0` - 2025-01-26
|
4
10
|
|
5
11
|
* 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.1.
|
3
|
+
Version: 1.1.1
|
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
|
@@ -324,8 +324,8 @@ Depending on your specific requirements, you can adjust the retry strategy. For
|
|
324
324
|
To customize the retry behavior, set the `retry_strategy` variable with the desired parameters:
|
325
325
|
|
326
326
|
- **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
|
327
|
+
- **base_delay**: float (in seconds) — specifies the delay between retries (default is 1 seconds).
|
328
|
+
- **max_attempts**: int — sets the maximum number of total request attempts (default is 10).
|
329
329
|
|
330
330
|
#### Default Retry Strategy
|
331
331
|
|
@@ -336,8 +336,8 @@ import earningscall
|
|
336
336
|
|
337
337
|
earningscall.retry_strategy = {
|
338
338
|
"strategy": "exponential",
|
339
|
-
"base_delay":
|
340
|
-
"max_attempts":
|
339
|
+
"base_delay": 1,
|
340
|
+
"max_attempts": 10,
|
341
341
|
}
|
342
342
|
```
|
343
343
|
|
@@ -350,7 +350,7 @@ import earningscall
|
|
350
350
|
|
351
351
|
earningscall.retry_strategy = {
|
352
352
|
"strategy": "exponential",
|
353
|
-
"base_delay":
|
353
|
+
"base_delay": 1,
|
354
354
|
"max_attempts": 1,
|
355
355
|
}
|
356
356
|
```
|
@@ -272,8 +272,8 @@ Depending on your specific requirements, you can adjust the retry strategy. For
|
|
272
272
|
To customize the retry behavior, set the `retry_strategy` variable with the desired parameters:
|
273
273
|
|
274
274
|
- **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
|
275
|
+
- **base_delay**: float (in seconds) — specifies the delay between retries (default is 1 seconds).
|
276
|
+
- **max_attempts**: int — sets the maximum number of total request attempts (default is 10).
|
277
277
|
|
278
278
|
#### Default Retry Strategy
|
279
279
|
|
@@ -284,8 +284,8 @@ import earningscall
|
|
284
284
|
|
285
285
|
earningscall.retry_strategy = {
|
286
286
|
"strategy": "exponential",
|
287
|
-
"base_delay":
|
288
|
-
"max_attempts":
|
287
|
+
"base_delay": 1,
|
288
|
+
"max_attempts": 10,
|
289
289
|
}
|
290
290
|
```
|
291
291
|
|
@@ -298,7 +298,7 @@ import earningscall
|
|
298
298
|
|
299
299
|
earningscall.retry_strategy = {
|
300
300
|
"strategy": "exponential",
|
301
|
-
"base_delay":
|
301
|
+
"base_delay": 1,
|
302
302
|
"max_attempts": 1,
|
303
303
|
}
|
304
304
|
```
|
@@ -5,10 +5,6 @@ from earningscall.symbols import Symbols, load_symbols
|
|
5
5
|
|
6
6
|
api_key: Optional[str] = None
|
7
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
|
-
}
|
8
|
+
retry_strategy: Optional[Dict[str, Union[str, int, float]]] = None
|
13
9
|
|
14
10
|
__all__ = ["get_company", "get_all_companies", "get_sp500_companies", "Symbols", "load_symbols"]
|
@@ -5,17 +5,23 @@ import logging
|
|
5
5
|
import os
|
6
6
|
import time
|
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
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "earningscall"
|
3
|
-
version = "1.1.
|
3
|
+
version = "1.1.1"
|
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)
|
@@ -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
|
|
@@ -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
|
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
|
{earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2022-advanced-data-level-2.yaml
RENAMED
File without changes
|
{earningscall-1.1.0 → earningscall-1.1.1}/tests/data/aapl-q1-2022-advanced-data-level-3.yaml
RENAMED
File without changes
|
{earningscall-1.1.0 → earningscall-1.1.1}/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.1.1}/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
|
File without changes
|