earningscall 1.0.2__py3-none-any.whl → 1.1.1__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.
- earningscall/__init__.py +2 -1
- earningscall/api.py +65 -12
- earningscall/errors.py +4 -0
- earningscall/symbols.py +11 -1
- {earningscall-1.0.2.dist-info → earningscall-1.1.1.dist-info}/METADATA +61 -2
- earningscall-1.1.1.dist-info/RECORD +14 -0
- earningscall-1.0.2.dist-info/RECORD +0 -14
- {earningscall-1.0.2.dist-info → earningscall-1.1.1.dist-info}/WHEEL +0 -0
- {earningscall-1.0.2.dist-info → earningscall-1.1.1.dist-info}/licenses/LICENSE +0 -0
earningscall/__init__.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Dict, Optional, Union
|
2
2
|
|
3
3
|
from earningscall.exports import get_company, get_all_companies, get_sp500_companies
|
4
4
|
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: Optional[Dict[str, Union[str, int, float]]] = None
|
8
9
|
|
9
10
|
__all__ = ["get_company", "get_all_companies", "get_sp500_companies", "Symbols", "load_symbols"]
|
earningscall/api.py
CHANGED
@@ -3,18 +3,25 @@ import platform
|
|
3
3
|
import urllib.parse
|
4
4
|
import logging
|
5
5
|
import os
|
6
|
+
import time
|
6
7
|
from importlib.metadata import PackageNotFoundError
|
7
|
-
from typing import Optional
|
8
|
+
from typing import Dict, Optional, Union
|
8
9
|
|
9
10
|
import requests
|
10
11
|
from requests_cache import CachedSession
|
11
12
|
|
12
13
|
import earningscall
|
14
|
+
from earningscall.errors import InvalidApiKeyError
|
13
15
|
|
14
16
|
log = logging.getLogger(__file__)
|
15
17
|
|
16
|
-
DOMAIN = os.environ.get("
|
18
|
+
DOMAIN = os.environ.get("EARNINGSCALL_DOMAIN", "earningscall.biz")
|
17
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
|
+
}
|
18
25
|
|
19
26
|
|
20
27
|
def get_api_key():
|
@@ -81,13 +88,27 @@ def get_headers():
|
|
81
88
|
}
|
82
89
|
|
83
90
|
|
91
|
+
def can_retry(response: requests.Response) -> bool:
|
92
|
+
if response.status_code == 429:
|
93
|
+
return True
|
94
|
+
# Check for 5XX errors
|
95
|
+
if response.status_code >= 500 and response.status_code < 600:
|
96
|
+
return True
|
97
|
+
return False
|
98
|
+
|
99
|
+
|
100
|
+
def is_success(response: requests.Response) -> bool:
|
101
|
+
# TODO: Do we need to check for 2xx status codes?
|
102
|
+
return response.status_code == 200
|
103
|
+
|
104
|
+
|
84
105
|
def do_get(
|
85
106
|
path: str,
|
86
107
|
use_cache: bool = False,
|
87
108
|
**kwargs,
|
88
109
|
) -> requests.Response:
|
89
110
|
"""
|
90
|
-
Do a GET request to the API.
|
111
|
+
Do a GET request to the API with exponential backoff retry for rate limits.
|
91
112
|
|
92
113
|
Args:
|
93
114
|
path (str): The path to request.
|
@@ -105,15 +126,47 @@ def do_get(
|
|
105
126
|
if log.isEnabledFor(logging.DEBUG):
|
106
127
|
full_url = f"{url}?{urllib.parse.urlencode(params)}"
|
107
128
|
log.debug(f"GET: {full_url}")
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
129
|
+
|
130
|
+
retry_strategy = earningscall.retry_strategy or DEFAULT_RETRY_STRATEGY
|
131
|
+
delay = retry_strategy["base_delay"]
|
132
|
+
max_attempts = int(retry_strategy["max_attempts"])
|
133
|
+
|
134
|
+
for attempt in range(max_attempts):
|
135
|
+
if use_cache and earningscall.enable_requests_cache:
|
136
|
+
response = cache_session().get(url, params=params)
|
137
|
+
else:
|
138
|
+
response = requests.get(
|
139
|
+
url,
|
140
|
+
params=params,
|
141
|
+
headers=get_headers(),
|
142
|
+
stream=kwargs.get("stream"),
|
143
|
+
)
|
144
|
+
|
145
|
+
if is_success(response):
|
146
|
+
return response
|
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
|
+
|
153
|
+
if not can_retry(response):
|
154
|
+
return response
|
155
|
+
|
156
|
+
if attempt < max_attempts - 1: # Don't sleep after the last attempt
|
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
|
161
|
+
else:
|
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?
|
164
|
+
log.warning(
|
165
|
+
f"Rate limited (429). Retrying in {wait_time} seconds... (Attempt {attempt + 1}/{max_attempts})"
|
166
|
+
)
|
167
|
+
time.sleep(wait_time)
|
168
|
+
|
169
|
+
return response # Return the last response if all retries failed
|
117
170
|
|
118
171
|
|
119
172
|
def get_events(exchange: str, symbol: str):
|
earningscall/errors.py
CHANGED
earningscall/symbols.py
CHANGED
@@ -8,7 +8,17 @@ from earningscall.errors import InsufficientApiAccessError
|
|
8
8
|
from earningscall.sectors import sector_to_index, industry_to_index, index_to_sector, index_to_industry
|
9
9
|
|
10
10
|
# WARNING: Add new indexes to the *END* of this list
|
11
|
-
EXCHANGES_IN_ORDER = [
|
11
|
+
EXCHANGES_IN_ORDER = [
|
12
|
+
"NYSE",
|
13
|
+
"NASDAQ",
|
14
|
+
"AMEX",
|
15
|
+
"TSX",
|
16
|
+
"TSXV",
|
17
|
+
"OTC",
|
18
|
+
"LSE",
|
19
|
+
"CBOE",
|
20
|
+
"STO",
|
21
|
+
]
|
12
22
|
|
13
23
|
log = logging.getLogger(__file__)
|
14
24
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: earningscall
|
3
|
-
Version: 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
|
@@ -32,7 +32,7 @@ License: MIT License
|
|
32
32
|
SOFTWARE.
|
33
33
|
License-File: LICENSE
|
34
34
|
Keywords: earning call app,earnings call,earnings call api,earnings call app,earnings call transcript api,earnings call transcripts,earnings call transcripts api,earnings calls,earnings transcript api,listen to earnings calls,transcripts,where to listen to earnings calls
|
35
|
-
Classifier: Development Status ::
|
35
|
+
Classifier: Development Status :: 5 - Production/Stable
|
36
36
|
Classifier: Intended Audience :: Developers
|
37
37
|
Classifier: License :: OSI Approved :: MIT License
|
38
38
|
Classifier: Programming Language :: Python :: 3
|
@@ -41,6 +41,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
41
41
|
Classifier: Programming Language :: Python :: 3.10
|
42
42
|
Classifier: Programming Language :: Python :: 3.11
|
43
43
|
Classifier: Programming Language :: Python :: 3.12
|
44
|
+
Classifier: Programming Language :: Python :: 3.13
|
44
45
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
45
46
|
Classifier: Typing :: Typed
|
46
47
|
Requires-Python: >=3.8
|
@@ -55,6 +56,7 @@ Description-Content-Type: text/markdown
|
|
55
56
|
[](https://github.com/EarningsCall/earningscall-python/actions?query=branch%3Amaster)
|
56
57
|
[](https://coveralls.io/github/EarningsCall/earningscall-python?branch=master)
|
57
58
|
[](https://pypi.org/project/earningscall/)
|
59
|
+
[](https://github.com/EarningsCall/earningscall-python)
|
58
60
|
|
59
61
|
[](https://www.python.org/)
|
60
62
|
|
@@ -310,3 +312,60 @@ import earningscall
|
|
310
312
|
|
311
313
|
earningscall.enable_requests_cache = False
|
312
314
|
```
|
315
|
+
|
316
|
+
### Retry Strategy
|
317
|
+
|
318
|
+
The library implements a flexible retry strategy to handle rate limiting and HTTP 5xx errors effectively. By default, it retries with increasing delays: 3 seconds, 6 seconds, 12 seconds, 24 seconds, and finally 48 seconds. If the request fails after five attempts, the library raises an exception.
|
319
|
+
|
320
|
+
#### Customizing the Retry Strategy
|
321
|
+
|
322
|
+
Depending on your specific requirements, you can adjust the retry strategy. For latency-sensitive applications, consider reducing the base delay and limiting the number of retry attempts. Conversely, for plans with lower rate limits, such as the "Starter" plan, a higher base delay with more retry attempts can improve reliability. For higher-rate-limit plans, such as "Enterprise," a shorter delay and fewer attempts may be more appropriate.
|
323
|
+
|
324
|
+
To customize the retry behavior, set the `retry_strategy` variable with the desired parameters:
|
325
|
+
|
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 1 seconds).
|
328
|
+
- **max_attempts**: int — sets the maximum number of total request attempts (default is 10).
|
329
|
+
|
330
|
+
#### Default Retry Strategy
|
331
|
+
|
332
|
+
Below is the default retry configuration:
|
333
|
+
|
334
|
+
```python
|
335
|
+
import earningscall
|
336
|
+
|
337
|
+
earningscall.retry_strategy = {
|
338
|
+
"strategy": "exponential",
|
339
|
+
"base_delay": 1,
|
340
|
+
"max_attempts": 10,
|
341
|
+
}
|
342
|
+
```
|
343
|
+
|
344
|
+
#### Disabling Retries
|
345
|
+
|
346
|
+
To disable retries entirely and limit the request to a single attempt, set `max_attempts` to `1`:
|
347
|
+
|
348
|
+
```python
|
349
|
+
import earningscall
|
350
|
+
|
351
|
+
earningscall.retry_strategy = {
|
352
|
+
"strategy": "exponential",
|
353
|
+
"base_delay": 1,
|
354
|
+
"max_attempts": 1,
|
355
|
+
}
|
356
|
+
```
|
357
|
+
|
358
|
+
#### Linear Retry Strategy
|
359
|
+
|
360
|
+
You can switch to a linear retry strategy by setting the `strategy` parameter to "linear":
|
361
|
+
|
362
|
+
```python
|
363
|
+
import earningscall
|
364
|
+
|
365
|
+
earningscall.retry_strategy = {
|
366
|
+
"strategy": "linear",
|
367
|
+
"base_delay": 1,
|
368
|
+
"max_attempts": 3,
|
369
|
+
}
|
370
|
+
```
|
371
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
earningscall/__init__.py,sha256=6yPTdUeBrKo4sVVW_hJHduTlQUDTw-30BwV5-2PlzYM,413
|
2
|
+
earningscall/api.py,sha256=honHHVfJISlWrikNYNE3CjXAkKJWqCa0Jk4q2cg9v1U,7633
|
3
|
+
earningscall/company.py,sha256=Ie3LwW5GjXsy3_it5F25JjHfbU3pW8Zefhpv3IjIk4U,6609
|
4
|
+
earningscall/errors.py,sha256=aLgwrrpMmYThYEZjCGOhqS57a-GoC0xj2BdbtJ20sy8,490
|
5
|
+
earningscall/event.py,sha256=Jf7KPvpeaF9KkeHe46LbL_HIYLXkyHrs3psq-ZY-bkI,692
|
6
|
+
earningscall/exports.py,sha256=YAo3vyX3PTgpKBFYwovVy-9797THrvMrdXWqLEHMtME,1425
|
7
|
+
earningscall/sectors.py,sha256=Xd6DLkAQ_fQkC2s-N9pReC8b_M3iy77OoFftoZj9FWY,5114
|
8
|
+
earningscall/symbols.py,sha256=NxabgKfZZI1YwDLUwh_MlNgyfkR9VZdcU-LqkGWwi28,6521
|
9
|
+
earningscall/transcript.py,sha256=P-CeTYhE5T78SXDHFEJ0AlVUFz2XPxDMtkeiorziBiw,1007
|
10
|
+
earningscall/utils.py,sha256=Qx8KhlumUdzyBSZRKMS6vpWlb8MGZpLKA4OffJaMdCE,1032
|
11
|
+
earningscall-1.1.1.dist-info/METADATA,sha256=3clQWAU4LnVOmlHgW4YMCXIXlxv_49niY0hZtdqjsRE,15446
|
12
|
+
earningscall-1.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
13
|
+
earningscall-1.1.1.dist-info/licenses/LICENSE,sha256=ktEB_UcRMg2cQlX9wiDs544xWncWizwS9mEZuGsCLrM,1069
|
14
|
+
earningscall-1.1.1.dist-info/RECORD,,
|
@@ -1,14 +0,0 @@
|
|
1
|
-
earningscall/__init__.py,sha256=0mANmPlE7LEWtOGzV2cmmlPfBIWBWlWRDkyqPHJ1jm8,333
|
2
|
-
earningscall/api.py,sha256=SNa07842eWWvVWI6wYGXP9TRDV_0tDE4g0lV_AwnQAs,5508
|
3
|
-
earningscall/company.py,sha256=Ie3LwW5GjXsy3_it5F25JjHfbU3pW8Zefhpv3IjIk4U,6609
|
4
|
-
earningscall/errors.py,sha256=EA-d6qIYgQs9csp8JptQiAaYoM0M9HhCGJgKA9GAWPg,440
|
5
|
-
earningscall/event.py,sha256=Jf7KPvpeaF9KkeHe46LbL_HIYLXkyHrs3psq-ZY-bkI,692
|
6
|
-
earningscall/exports.py,sha256=YAo3vyX3PTgpKBFYwovVy-9797THrvMrdXWqLEHMtME,1425
|
7
|
-
earningscall/sectors.py,sha256=Xd6DLkAQ_fQkC2s-N9pReC8b_M3iy77OoFftoZj9FWY,5114
|
8
|
-
earningscall/symbols.py,sha256=842DO8zfglTvPKb_6QkQwAhIAnxqy_zasijBcY-ctm4,6482
|
9
|
-
earningscall/transcript.py,sha256=P-CeTYhE5T78SXDHFEJ0AlVUFz2XPxDMtkeiorziBiw,1007
|
10
|
-
earningscall/utils.py,sha256=Qx8KhlumUdzyBSZRKMS6vpWlb8MGZpLKA4OffJaMdCE,1032
|
11
|
-
earningscall-1.0.2.dist-info/METADATA,sha256=jXMRbhV7u7vM42arV7ySlEvx1Ms6DbdIoNr-0GjP6lg,13265
|
12
|
-
earningscall-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
13
|
-
earningscall-1.0.2.dist-info/licenses/LICENSE,sha256=ktEB_UcRMg2cQlX9wiDs544xWncWizwS9mEZuGsCLrM,1069
|
14
|
-
earningscall-1.0.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|