earningscall 1.0.2__py3-none-any.whl → 1.1.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.
earningscall/__init__.py CHANGED
@@ -1,9 +1,14 @@
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: Dict[str, Union[str, int, float]] = {
9
+ "strategy": "exponential",
10
+ "base_delay": 3,
11
+ "max_attempts": 5,
12
+ }
8
13
 
9
14
  __all__ = ["get_company", "get_all_companies", "get_sp500_companies", "Symbols", "load_symbols"]
earningscall/api.py CHANGED
@@ -3,6 +3,7 @@ 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
8
  from typing import Optional
8
9
 
@@ -81,13 +82,27 @@ def get_headers():
81
82
  }
82
83
 
83
84
 
85
+ def can_retry(response: requests.Response) -> bool:
86
+ if response.status_code == 429:
87
+ return True
88
+ # Check for 5XX errors
89
+ if response.status_code >= 500 and response.status_code < 600:
90
+ return True
91
+ return False
92
+
93
+
94
+ def is_success(response: requests.Response) -> bool:
95
+ # TODO: Do we need to check for 2xx status codes?
96
+ return response.status_code == 200
97
+
98
+
84
99
  def do_get(
85
100
  path: str,
86
101
  use_cache: bool = False,
87
102
  **kwargs,
88
103
  ) -> requests.Response:
89
104
  """
90
- Do a GET request to the API.
105
+ Do a GET request to the API with exponential backoff retry for rate limits.
91
106
 
92
107
  Args:
93
108
  path (str): The path to request.
@@ -105,15 +120,40 @@ def do_get(
105
120
  if log.isEnabledFor(logging.DEBUG):
106
121
  full_url = f"{url}?{urllib.parse.urlencode(params)}"
107
122
  log.debug(f"GET: {full_url}")
108
- if use_cache and earningscall.enable_requests_cache:
109
- return cache_session().get(url, params=params)
110
- else:
111
- return requests.get(
112
- url,
113
- params=params,
114
- headers=get_headers(),
115
- stream=kwargs.get("stream"),
116
- )
123
+
124
+ delay = earningscall.retry_strategy["base_delay"]
125
+ max_attempts = int(earningscall.retry_strategy["max_attempts"])
126
+
127
+ for attempt in range(max_attempts):
128
+ if use_cache and earningscall.enable_requests_cache:
129
+ response = cache_session().get(url, params=params)
130
+ else:
131
+ response = requests.get(
132
+ url,
133
+ params=params,
134
+ headers=get_headers(),
135
+ stream=kwargs.get("stream"),
136
+ )
137
+
138
+ if is_success(response):
139
+ return response
140
+
141
+ if not can_retry(response):
142
+ return response
143
+
144
+ if attempt < max_attempts - 1: # Don't sleep after the last attempt
145
+ if earningscall.retry_strategy["strategy"] == "exponential":
146
+ wait_time = delay * (2**attempt) # Exponential backoff: 3s -> 6s -> 12s -> 24s -> 48s
147
+ elif earningscall.retry_strategy["strategy"] == "linear":
148
+ wait_time = delay * (attempt + 1) # Linear backoff: 3s -> 6s -> 9s -> 12s -> 15s
149
+ else:
150
+ raise ValueError("Invalid retry strategy. Must be one of: 'exponential', 'linear'")
151
+ log.warning(
152
+ f"Rate limited (429). Retrying in {wait_time} seconds... (Attempt {attempt + 1}/{max_attempts})"
153
+ )
154
+ time.sleep(wait_time)
155
+
156
+ return response # Return the last response if all retries failed
117
157
 
118
158
 
119
159
  def get_events(exchange: str, symbol: str):
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 = ["NYSE", "NASDAQ", "AMEX", "TSX", "TSXV", "OTC", "LSE", "CBOE", "STO"]
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.0.2
3
+ Version: 1.1.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
@@ -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 :: 3 - Alpha
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
  [![Build Status](https://github.com/EarningsCall/earningscall-python/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/EarningsCall/earningscall-python/actions?query=branch%3Amaster)
56
57
  [![Coverage Status](https://coveralls.io/repos/github/EarningsCall/earningscall-python/badge.svg?branch=master)](https://coveralls.io/github/EarningsCall/earningscall-python?branch=master)
57
58
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/earningscall?color=blue)](https://pypi.org/project/earningscall/)
59
+ [![GitHub Stars](https://img.shields.io/github/stars/EarningsCall/earningscall-python.svg?style=social&label=Star)](https://github.com/EarningsCall/earningscall-python)
58
60
 
59
61
  [![Python](https://img.shields.io/badge/Python-14354C?style=for-the-badge&logo=python&logoColor=white)](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 3 seconds).
328
+ - **max_attempts**: int — sets the maximum number of total request attempts (default is 5).
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": 3,
340
+ "max_attempts": 5,
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": 3,
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
+
@@ -1,14 +1,14 @@
1
- earningscall/__init__.py,sha256=0mANmPlE7LEWtOGzV2cmmlPfBIWBWlWRDkyqPHJ1jm8,333
2
- earningscall/api.py,sha256=SNa07842eWWvVWI6wYGXP9TRDV_0tDE4g0lV_AwnQAs,5508
1
+ earningscall/__init__.py,sha256=_7Xxi8zoSt98fDwzSpEfjrxgiJsc7UobZBwtsAbt3Lg,477
2
+ earningscall/api.py,sha256=H4Z_GcD6RnTCkwGE--imCOsySDLkD9qf4Rj5j-dZHkw,7088
3
3
  earningscall/company.py,sha256=Ie3LwW5GjXsy3_it5F25JjHfbU3pW8Zefhpv3IjIk4U,6609
4
4
  earningscall/errors.py,sha256=EA-d6qIYgQs9csp8JptQiAaYoM0M9HhCGJgKA9GAWPg,440
5
5
  earningscall/event.py,sha256=Jf7KPvpeaF9KkeHe46LbL_HIYLXkyHrs3psq-ZY-bkI,692
6
6
  earningscall/exports.py,sha256=YAo3vyX3PTgpKBFYwovVy-9797THrvMrdXWqLEHMtME,1425
7
7
  earningscall/sectors.py,sha256=Xd6DLkAQ_fQkC2s-N9pReC8b_M3iy77OoFftoZj9FWY,5114
8
- earningscall/symbols.py,sha256=842DO8zfglTvPKb_6QkQwAhIAnxqy_zasijBcY-ctm4,6482
8
+ earningscall/symbols.py,sha256=NxabgKfZZI1YwDLUwh_MlNgyfkR9VZdcU-LqkGWwi28,6521
9
9
  earningscall/transcript.py,sha256=P-CeTYhE5T78SXDHFEJ0AlVUFz2XPxDMtkeiorziBiw,1007
10
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,,
11
+ earningscall-1.1.0.dist-info/METADATA,sha256=U7DpkmtPIfIRGfpIB5WM77okwWH2XrLxKRI_adOhv1k,15444
12
+ earningscall-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
+ earningscall-1.1.0.dist-info/licenses/LICENSE,sha256=ktEB_UcRMg2cQlX9wiDs544xWncWizwS9mEZuGsCLrM,1069
14
+ earningscall-1.1.0.dist-info/RECORD,,