earningscall 0.0.10__tar.gz → 0.0.12__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-0.0.10 → earningscall-0.0.12}/.github/workflows/release.yml +40 -35
- earningscall-0.0.12/.github/workflows/test.yml +63 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/.gitignore +5 -1
- {earningscall-0.0.10 → earningscall-0.0.12}/DEVELOPMENT.md +23 -3
- {earningscall-0.0.10 → earningscall-0.0.12}/PKG-INFO +2 -3
- {earningscall-0.0.10 → earningscall-0.0.12}/README.md +0 -2
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/__init__.py +2 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/api.py +8 -6
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/company.py +16 -16
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/errors.py +1 -2
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/event.py +3 -2
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/exports.py +4 -4
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/sectors.py +5 -8
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/symbols.py +32 -19
- earningscall-0.0.12/hatch.toml +46 -0
- earningscall-0.0.12/pyproject.toml +127 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/scripts/get_all_company_transcripts.py +1 -1
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/test_earnings_event.py +7 -5
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/test_get_transcript.py +6 -4
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/test_symbols.py +27 -6
- earningscall-0.0.10/pyproject.toml +0 -74
- earningscall-0.0.10/requirements-dev.lock +0 -46
- earningscall-0.0.10/requirements.lock +0 -35
- {earningscall-0.0.10 → earningscall-0.0.12}/.python-version +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/CHANGELOG.md +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/LICENSE +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/TODO.md +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/transcript.py +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/earningscall/utils.py +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/scripts/get_single_transcript.py +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/scripts/list_companies.py +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/setup.cfg +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/data/demo-symbols-v2-alpha.yaml +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/data/demo-symbols-v2.yaml +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/data/msft-transcript-response.yaml +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/data/symbols-v2.yaml +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/data/symbols.txt +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/data/symbols.yaml +0 -0
- {earningscall-0.0.10 → earningscall-0.0.12}/tests/test_helper.py +0 -0
@@ -1,9 +1,18 @@
|
|
1
1
|
# https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
|
2
|
-
name: Build
|
2
|
+
name: Build
|
3
3
|
|
4
|
-
on:
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
tags:
|
7
|
+
- v*
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
pull_request:
|
11
|
+
branches:
|
12
|
+
- master
|
5
13
|
|
6
14
|
jobs:
|
15
|
+
|
7
16
|
build:
|
8
17
|
name: Build distribution 📦
|
9
18
|
runs-on: ubuntu-latest
|
@@ -20,22 +29,16 @@ jobs:
|
|
20
29
|
pip install
|
21
30
|
build
|
22
31
|
--user
|
23
|
-
- name:
|
24
|
-
run:
|
25
|
-
curl -sSf https://rye.astral.sh/get | RYE_NO_AUTO_INSTALL=1 RYE_INSTALL_OPTION="--yes" bash
|
26
|
-
# $(HOME)/.rye/shims/rye pin $(PYTHON_VERSION)
|
27
|
-
# $(HOME)/.rye/shims/rye sync --no-lock
|
28
|
-
# https://rye.astral.sh/guide/publish/
|
32
|
+
- name: Build
|
33
|
+
run: python -m build
|
29
34
|
- name: Build a binary wheel and sdist
|
30
|
-
run:
|
31
|
-
source ~/.rye/env
|
32
|
-
rye sync
|
33
|
-
rye build
|
35
|
+
run: python -m build
|
34
36
|
- name: Store the distribution packages
|
35
37
|
uses: actions/upload-artifact@v3
|
36
38
|
with:
|
37
39
|
name: python-package-distributions
|
38
40
|
path: dist/
|
41
|
+
if-no-files-found: error
|
39
42
|
|
40
43
|
publish-to-pypi:
|
41
44
|
name: >-
|
@@ -102,26 +105,28 @@ jobs:
|
|
102
105
|
'${{ github.ref_name }}' dist/**
|
103
106
|
--repo '${{ github.repository }}'
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
108
|
+
# For now, don't publish to TestPyPI (as it needs a unique version each time).
|
109
|
+
# Is there a solution for this?
|
110
|
+
# publish-to-testpypi:
|
111
|
+
# name: Publish Python 🐍 distribution 📦 to TestPyPI
|
112
|
+
# needs:
|
113
|
+
# - build
|
114
|
+
# runs-on: ubuntu-latest
|
115
|
+
#
|
116
|
+
# environment:
|
117
|
+
# name: testpypi
|
118
|
+
# url: https://test.pypi.org/p/earningscall
|
119
|
+
#
|
120
|
+
# permissions:
|
121
|
+
# id-token: write # IMPORTANT: mandatory for trusted publishing
|
122
|
+
#
|
123
|
+
# steps:
|
124
|
+
# - name: Download all the dists
|
125
|
+
# uses: actions/download-artifact@v3
|
126
|
+
# with:
|
127
|
+
# name: python-package-distributions
|
128
|
+
# path: dist/
|
129
|
+
# - name: Publish distribution 📦 to TestPyPI
|
130
|
+
# uses: pypa/gh-action-pypi-publish@release/v1
|
131
|
+
# with:
|
132
|
+
# repository-url: https://test.pypi.org/legacy/
|
@@ -0,0 +1,63 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
|
11
|
+
concurrency:
|
12
|
+
group: test-${{ github.head_ref }}
|
13
|
+
cancel-in-progress: true
|
14
|
+
|
15
|
+
env:
|
16
|
+
PYTHONUNBUFFERED: "1"
|
17
|
+
FORCE_COLOR: "1"
|
18
|
+
|
19
|
+
jobs:
|
20
|
+
run:
|
21
|
+
name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}
|
22
|
+
runs-on: ${{ matrix.os }}
|
23
|
+
strategy:
|
24
|
+
fail-fast: false
|
25
|
+
matrix:
|
26
|
+
os: [ubuntu-latest]
|
27
|
+
python-version: ['3.9']
|
28
|
+
|
29
|
+
steps:
|
30
|
+
- uses: actions/checkout@v3
|
31
|
+
|
32
|
+
- name: Set up Python ${{ matrix.python-version }}
|
33
|
+
uses: actions/setup-python@v4
|
34
|
+
with:
|
35
|
+
python-version: ${{ matrix.python-version }}
|
36
|
+
|
37
|
+
- name: Install Hatch
|
38
|
+
run: pip install --upgrade hatch
|
39
|
+
|
40
|
+
- if: matrix.python-version == '3.9' && runner.os == 'Linux'
|
41
|
+
name: Lint
|
42
|
+
run: hatch run lint:all
|
43
|
+
|
44
|
+
- name: Run tests and track code coverage
|
45
|
+
run: hatch run cov
|
46
|
+
|
47
|
+
run-container-matrix:
|
48
|
+
name: Container matrix on Linux
|
49
|
+
runs-on: ubuntu-latest
|
50
|
+
|
51
|
+
steps:
|
52
|
+
- uses: actions/checkout@v3
|
53
|
+
|
54
|
+
- name: Set up Python 3.10
|
55
|
+
uses: actions/setup-python@v4
|
56
|
+
with:
|
57
|
+
python-version: '3.10'
|
58
|
+
|
59
|
+
- name: Install Hatch
|
60
|
+
run: pip install --upgrade hatch hatch-containers
|
61
|
+
|
62
|
+
- name: Run tests in container matrix
|
63
|
+
run: hatch run all:test
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# Development
|
2
2
|
|
3
|
-
TODO: Add
|
4
|
-
|
5
|
-
This project uses Rye: https://rye.astral.sh/
|
3
|
+
TODO: Add hatch installation instructions.
|
6
4
|
|
7
5
|
|
8
6
|
### Saving Server-Side Responses for a Mocked Unit test
|
@@ -35,3 +33,25 @@ git commit -a
|
|
35
33
|
git tag v0.0.7
|
36
34
|
git push --atomic origin master v0.0.7
|
37
35
|
```
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
### Manually Running Scripts
|
40
|
+
|
41
|
+
Use the library to get a single transcript from the API:
|
42
|
+
|
43
|
+
```shell
|
44
|
+
python -m scripts.get_single_transcript
|
45
|
+
```
|
46
|
+
|
47
|
+
Get all transcripts for a company:
|
48
|
+
|
49
|
+
```shell
|
50
|
+
python -m scripts.get_all_company_transcripts
|
51
|
+
```
|
52
|
+
|
53
|
+
List all companies:
|
54
|
+
|
55
|
+
```shell
|
56
|
+
python -m scripts.list_companies
|
57
|
+
```
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: earningscall
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.12
|
4
4
|
Summary: The EarningsCall Python library.
|
5
5
|
Project-URL: Homepage, https://earningscall.biz
|
6
6
|
Project-URL: Documentation, https://github.com/EarningsCall/earningscall-python
|
@@ -34,6 +34,7 @@ License-File: LICENSE
|
|
34
34
|
Requires-Python: >=3.8
|
35
35
|
Requires-Dist: dataclasses-json>=0.6.4
|
36
36
|
Requires-Dist: dataclasses>=0.6
|
37
|
+
Requires-Dist: requests-cache>=1.2.0
|
37
38
|
Requires-Dist: requests>=2.30.0
|
38
39
|
Description-Content-Type: text/markdown
|
39
40
|
|
@@ -129,10 +130,8 @@ To gain access to 5,000+ companies please [signup here](https://earningscall.biz
|
|
129
130
|
Once you have access to your API key, you can set the API Key like this:
|
130
131
|
|
131
132
|
```python
|
132
|
-
|
133
133
|
import earningscall
|
134
134
|
|
135
|
-
|
136
135
|
earningscall.api_key = "YOUR SECRET API KEY GOES HERE"
|
137
136
|
```
|
138
137
|
|
@@ -90,10 +90,8 @@ To gain access to 5,000+ companies please [signup here](https://earningscall.biz
|
|
90
90
|
Once you have access to your API key, you can set the API Key like this:
|
91
91
|
|
92
92
|
```python
|
93
|
-
|
94
93
|
import earningscall
|
95
94
|
|
96
|
-
|
97
95
|
earningscall.api_key = "YOUR SECRET API KEY GOES HERE"
|
98
96
|
```
|
99
97
|
|
@@ -27,8 +27,7 @@ def is_demo_account():
|
|
27
27
|
return get_api_key() == "demo"
|
28
28
|
|
29
29
|
|
30
|
-
def get_events(exchange: str,
|
31
|
-
symbol: str):
|
30
|
+
def get_events(exchange: str, symbol: str):
|
32
31
|
|
33
32
|
log.debug(f"get_events exchange: {exchange} symbol: {symbol}")
|
34
33
|
params = {
|
@@ -42,10 +41,7 @@ def get_events(exchange: str,
|
|
42
41
|
return response.json()
|
43
42
|
|
44
43
|
|
45
|
-
def get_transcript(exchange: str,
|
46
|
-
symbol: str,
|
47
|
-
year: int,
|
48
|
-
quarter: int) -> Optional[str]:
|
44
|
+
def get_transcript(exchange: str, symbol: str, year: int, quarter: int) -> Optional[str]:
|
49
45
|
|
50
46
|
log.debug(f"get_transcript year: {year} quarter: {quarter}")
|
51
47
|
params = {
|
@@ -80,3 +76,9 @@ def get_sp500_companies_txt_file():
|
|
80
76
|
if response.status_code != 200:
|
81
77
|
return None
|
82
78
|
return response.text
|
79
|
+
|
80
|
+
|
81
|
+
# def do_something():
|
82
|
+
# session = CachedSession('demo_cache', cache_control=True)
|
83
|
+
#
|
84
|
+
# # CachedSession()
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import logging
|
2
|
-
from typing import Optional
|
2
|
+
from typing import Optional, List
|
3
3
|
|
4
4
|
from earningscall import api
|
5
5
|
from earningscall.event import EarningsEvent
|
@@ -12,8 +12,8 @@ log = logging.getLogger(__file__)
|
|
12
12
|
class Company:
|
13
13
|
|
14
14
|
company_info: CompanyInfo
|
15
|
-
name: str
|
16
|
-
_events: [EarningsEvent]
|
15
|
+
name: Optional[str]
|
16
|
+
_events: Optional[List[EarningsEvent]]
|
17
17
|
|
18
18
|
def __init__(self, company_info: CompanyInfo):
|
19
19
|
if not company_info:
|
@@ -25,31 +25,31 @@ class Company:
|
|
25
25
|
def __str__(self):
|
26
26
|
return str(self.name)
|
27
27
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
def _get_events(self):
|
28
|
+
def _get_events(self) -> List[EarningsEvent]:
|
29
|
+
if not self.company_info.exchange or not self.company_info.symbol:
|
30
|
+
return []
|
32
31
|
raw_response = api.get_events(self.company_info.exchange, self.company_info.symbol)
|
33
32
|
if not raw_response:
|
34
33
|
return []
|
35
|
-
return [EarningsEvent.from_dict(event) for event in raw_response["events"]]
|
34
|
+
return [EarningsEvent.from_dict(event) for event in raw_response["events"]] # type: ignore
|
36
35
|
|
37
|
-
def events(self) -> [EarningsEvent]:
|
36
|
+
def events(self) -> List[EarningsEvent]:
|
38
37
|
if not self._events:
|
39
38
|
self._events = self._get_events()
|
40
39
|
return self._events
|
41
40
|
|
42
|
-
def get_transcript(
|
43
|
-
|
44
|
-
|
45
|
-
event: Optional[EarningsEvent] = None) -> Optional[Transcript]:
|
41
|
+
def get_transcript(
|
42
|
+
self, year: Optional[int] = None, quarter: Optional[int] = None, event: Optional[EarningsEvent] = None
|
43
|
+
) -> Optional[Transcript]:
|
46
44
|
|
45
|
+
if not self.company_info.exchange or not self.company_info.symbol:
|
46
|
+
return None
|
47
47
|
if (not year or not quarter) and event:
|
48
48
|
year = event.year
|
49
49
|
quarter = event.quarter
|
50
|
-
|
50
|
+
if (not year or not quarter) and not event:
|
51
51
|
raise ValueError("Must specify either event or year and quarter")
|
52
|
-
resp = api.get_transcript(self.company_info.exchange, self.company_info.symbol, year, quarter)
|
52
|
+
resp = api.get_transcript(self.company_info.exchange, self.company_info.symbol, year, quarter) # type: ignore
|
53
53
|
if not resp:
|
54
54
|
return None
|
55
|
-
return Transcript.from_dict(resp)
|
55
|
+
return Transcript.from_dict(resp) # type: ignore
|
@@ -16,6 +16,7 @@ class EarningsEvent:
|
|
16
16
|
"""
|
17
17
|
EarningsEvent
|
18
18
|
"""
|
19
|
+
|
19
20
|
year: int
|
20
21
|
quarter: int
|
21
22
|
conference_date: Optional[datetime] = field(
|
@@ -23,6 +24,6 @@ class EarningsEvent:
|
|
23
24
|
metadata=config(
|
24
25
|
encoder=lambda date: date.isoformat() if date else None,
|
25
26
|
decoder=lambda date: datetime.fromisoformat(date) if date else None,
|
26
|
-
mm_field=fields.DateTime(format="iso")
|
27
|
-
)
|
27
|
+
mm_field=fields.DateTime(format="iso"),
|
28
|
+
),
|
28
29
|
)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional, Iterator
|
2
2
|
|
3
3
|
from earningscall.api import get_sp500_companies_txt_file
|
4
4
|
from earningscall.company import Company
|
@@ -12,15 +12,15 @@ def get_company(symbol: str) -> Optional[Company]:
|
|
12
12
|
return None
|
13
13
|
|
14
14
|
|
15
|
-
def get_all_companies() -> [Company]:
|
15
|
+
def get_all_companies() -> Iterator[Company]:
|
16
16
|
for company_info in get_symbols().get_all():
|
17
17
|
yield Company(company_info=company_info)
|
18
18
|
|
19
19
|
|
20
|
-
def get_sp500_companies() -> [Company]:
|
20
|
+
def get_sp500_companies() -> Iterator[Company]:
|
21
21
|
resp = get_sp500_companies_txt_file()
|
22
22
|
if not resp:
|
23
|
-
return
|
23
|
+
return
|
24
24
|
for ticker_symbol in resp.split("\n"):
|
25
25
|
company_info = get_symbols().lookup_company(ticker_symbol)
|
26
26
|
if company_info:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
|
-
|
3
|
+
from typing import Optional
|
4
4
|
|
5
5
|
log = logging.getLogger(__file__)
|
6
6
|
sectors_file_name = "sectors.json"
|
@@ -17,7 +17,7 @@ SECTORS_IN_ORDER = [
|
|
17
17
|
'Industrials',
|
18
18
|
'Real Estate',
|
19
19
|
'Technology',
|
20
|
-
'Utilities'
|
20
|
+
'Utilities',
|
21
21
|
]
|
22
22
|
|
23
23
|
|
@@ -166,7 +166,7 @@ INDUSTRIES_IN_ORDER = [
|
|
166
166
|
'Utilities - Regulated Gas',
|
167
167
|
'Utilities - Regulated Water',
|
168
168
|
'Utilities - Renewable',
|
169
|
-
'Waste Management'
|
169
|
+
'Waste Management',
|
170
170
|
]
|
171
171
|
|
172
172
|
|
@@ -198,9 +198,7 @@ def industry_to_index(_industry: str) -> int:
|
|
198
198
|
|
199
199
|
class Sectors:
|
200
200
|
|
201
|
-
def __init__(self,
|
202
|
-
sectors: set = None,
|
203
|
-
industries: set = None):
|
201
|
+
def __init__(self, sectors: Optional[set] = None, industries: Optional[set] = None):
|
204
202
|
if sectors:
|
205
203
|
self.sectors = sectors
|
206
204
|
else:
|
@@ -218,7 +216,7 @@ class Sectors:
|
|
218
216
|
if industry is not None:
|
219
217
|
self.industries.add(industry)
|
220
218
|
|
221
|
-
def to_dicts(self) ->
|
219
|
+
def to_dicts(self) -> dict:
|
222
220
|
return {
|
223
221
|
"sectors": list(self.sectors),
|
224
222
|
"industries": list(self.industries),
|
@@ -231,4 +229,3 @@ class Sectors:
|
|
231
229
|
def from_json(json_str):
|
232
230
|
data = json.loads(json_str)
|
233
231
|
return Sectors(set(data["sectors"]), set(data["industries"]))
|
234
|
-
|
@@ -2,7 +2,7 @@ import json
|
|
2
2
|
import logging
|
3
3
|
import re
|
4
4
|
from collections import defaultdict
|
5
|
-
from typing import Optional
|
5
|
+
from typing import Optional, Iterator, List
|
6
6
|
|
7
7
|
from earningscall.api import get_symbols_v2, is_demo_account
|
8
8
|
from earningscall.errors import InsufficientApiAccessError
|
@@ -14,7 +14,9 @@ EXCHANGES_IN_ORDER = ["NYSE", "NASDAQ", "AMEX", "TSX", "TSXV", "OTC"]
|
|
14
14
|
log = logging.getLogger(__file__)
|
15
15
|
|
16
16
|
|
17
|
-
def exchange_to_index(_exchange: str) -> int:
|
17
|
+
def exchange_to_index(_exchange: Optional[str]) -> int:
|
18
|
+
if not _exchange:
|
19
|
+
return -1
|
18
20
|
try:
|
19
21
|
return EXCHANGES_IN_ORDER.index(_exchange)
|
20
22
|
except ValueError:
|
@@ -110,7 +112,7 @@ class Symbols:
|
|
110
112
|
if len(self.by_exchange_and_sym) == size_before:
|
111
113
|
log.debug(f"Duplicate: {_sym}")
|
112
114
|
|
113
|
-
def get_all(self) -> [CompanyInfo]:
|
115
|
+
def get_all(self) -> Iterator[CompanyInfo]:
|
114
116
|
for _exchange_symbol, _symbol in self.by_exchange_and_sym.items():
|
115
117
|
yield _symbol
|
116
118
|
|
@@ -129,8 +131,10 @@ class Symbols:
|
|
129
131
|
except KeyError:
|
130
132
|
pass
|
131
133
|
if is_demo_account():
|
132
|
-
raise InsufficientApiAccessError(
|
133
|
-
|
134
|
+
raise InsufficientApiAccessError(
|
135
|
+
f"\"{symbol}\" requires an API Key for access. To get your API Key,"
|
136
|
+
f" see: https://earningscall.biz/api-pricing"
|
137
|
+
)
|
134
138
|
return None
|
135
139
|
|
136
140
|
def remove_exchange_symbol(self, exchange_symbol: str):
|
@@ -142,11 +146,13 @@ class Symbols:
|
|
142
146
|
def remove_keys(symbol_as_dict: dict, keys_to_remove: set):
|
143
147
|
return {key: value for key, value in symbol_as_dict.items() if key not in keys_to_remove}
|
144
148
|
|
145
|
-
def without_security_names(self) -> [dict]:
|
146
|
-
return [
|
147
|
-
|
149
|
+
def without_security_names(self) -> List[dict]:
|
150
|
+
return [
|
151
|
+
self.remove_keys(symbol_as_dict, {"security_name", "sector", "industry"})
|
152
|
+
for symbol_as_dict in self.to_dicts()
|
153
|
+
]
|
148
154
|
|
149
|
-
def to_dicts(self) -> [dict]:
|
155
|
+
def to_dicts(self) -> List[dict]:
|
150
156
|
return [__symbol.__dict__ for __symbol in self.get_all()]
|
151
157
|
|
152
158
|
def to_json(self, remove_security_names: bool = False) -> str:
|
@@ -154,9 +160,14 @@ class Symbols:
|
|
154
160
|
return json.dumps(self.without_security_names())
|
155
161
|
return json.dumps(self.to_dicts())
|
156
162
|
|
157
|
-
|
158
|
-
|
159
|
-
|
163
|
+
# TODO: Test this
|
164
|
+
# def to_json_v2(self) -> str:
|
165
|
+
# return json.dumps(
|
166
|
+
# [
|
167
|
+
# [exchange_to_index(__symbol.exchange), __symbol.company_info, __symbol.name]
|
168
|
+
# for __symbol in self.get_all()
|
169
|
+
# ]
|
170
|
+
# )
|
160
171
|
|
161
172
|
def to_txt(self) -> str:
|
162
173
|
exchange_symbol_names = [__symbol.to_txt_row() for __symbol in self.get_all()]
|
@@ -195,13 +206,15 @@ class Symbols:
|
|
195
206
|
__symbols = Symbols()
|
196
207
|
for line in txt_str.split("\n"):
|
197
208
|
_exchange_index, _symbol, _name, _sector_index, _industry_index = line.split("\t")
|
198
|
-
__symbols.add(
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
209
|
+
__symbols.add(
|
210
|
+
CompanyInfo(
|
211
|
+
exchange=index_to_exchange(int(_exchange_index)),
|
212
|
+
symbol=_symbol,
|
213
|
+
name=_name,
|
214
|
+
sector=index_to_sector(int(_sector_index)),
|
215
|
+
industry=index_to_industry(int(_industry_index)),
|
216
|
+
)
|
217
|
+
)
|
205
218
|
return __symbols
|
206
219
|
|
207
220
|
@staticmethod
|
@@ -0,0 +1,46 @@
|
|
1
|
+
[envs.default]
|
2
|
+
dependencies = [
|
3
|
+
"coverage[toml]>=6.5",
|
4
|
+
"pytest",
|
5
|
+
"responses",
|
6
|
+
]
|
7
|
+
[envs.default.scripts]
|
8
|
+
test = "pytest {args:tests}"
|
9
|
+
test-cov = "coverage run -m pytest {args:tests}"
|
10
|
+
cov-report = [
|
11
|
+
"- coverage combine",
|
12
|
+
"coverage report --show-missing",
|
13
|
+
]
|
14
|
+
cov = [
|
15
|
+
"test-cov",
|
16
|
+
"cov-report",
|
17
|
+
]
|
18
|
+
|
19
|
+
[envs.all]
|
20
|
+
type = "container"
|
21
|
+
|
22
|
+
[[envs.all.matrix]]
|
23
|
+
python = ["3.8", "3.9", "3.10", "3.11"]
|
24
|
+
|
25
|
+
[envs.lint]
|
26
|
+
detached = true
|
27
|
+
dependencies = [
|
28
|
+
"black>=22.10.0",
|
29
|
+
"mypy>=0.991",
|
30
|
+
"ruff>=0.0.166",
|
31
|
+
]
|
32
|
+
[envs.lint.scripts]
|
33
|
+
typing = "mypy --install-types --non-interactive {args:earningscall tests}"
|
34
|
+
style = [
|
35
|
+
"ruff {args:.}",
|
36
|
+
"black --check --diff {args:.}",
|
37
|
+
]
|
38
|
+
fmt = [
|
39
|
+
"black {args:.}",
|
40
|
+
"ruff --fix {args:.}",
|
41
|
+
"style",
|
42
|
+
]
|
43
|
+
all = [
|
44
|
+
"style",
|
45
|
+
"typing",
|
46
|
+
]
|
@@ -0,0 +1,127 @@
|
|
1
|
+
[project]
|
2
|
+
name = "earningscall"
|
3
|
+
version = "0.0.12"
|
4
|
+
description = "The EarningsCall Python library."
|
5
|
+
readme = "README.md"
|
6
|
+
authors = [
|
7
|
+
{name = "EarningsCall", email = "dev@earningscall.biz"},
|
8
|
+
]
|
9
|
+
requires-python = ">= 3.8"
|
10
|
+
dependencies = [
|
11
|
+
"dataclasses>=0.6",
|
12
|
+
"dataclasses-json>=0.6.4",
|
13
|
+
"requests>=2.30.0",
|
14
|
+
"requests-cache>=1.2.0",
|
15
|
+
]
|
16
|
+
license = { file = "LICENSE" }
|
17
|
+
|
18
|
+
[project.urls]
|
19
|
+
Homepage = "https://earningscall.biz"
|
20
|
+
Documentation = "https://github.com/EarningsCall/earningscall-python"
|
21
|
+
Repository = "https://github.com/EarningsCall/earningscall-python"
|
22
|
+
Issues = "https://github.com/EarningsCall/earningscall-python/issues"
|
23
|
+
Source = "https://github.com/EarningsCall/earningscall-python"
|
24
|
+
Changelog = "https://github.com/EarningsCall/earningscall-python/blob/master/CHANGELOG.md"
|
25
|
+
|
26
|
+
[build-system]
|
27
|
+
requires = ["hatchling"]
|
28
|
+
build-backend = "hatchling.build"
|
29
|
+
|
30
|
+
|
31
|
+
[tool.hatch.metadata]
|
32
|
+
allow-direct-references = true
|
33
|
+
|
34
|
+
|
35
|
+
[tool.hatch.build.targets.wheel]
|
36
|
+
packages = ["earningscall"]
|
37
|
+
|
38
|
+
[tool.hatch.version]
|
39
|
+
path = "hatch_init/__about__.py"
|
40
|
+
|
41
|
+
[tool.hatch.envs.default]
|
42
|
+
dependencies = [
|
43
|
+
"pytest",
|
44
|
+
"pytest-cov",
|
45
|
+
]
|
46
|
+
[tool.hatch.envs.default.scripts]
|
47
|
+
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=hatch_init --cov=tests"
|
48
|
+
no-cov = "cov --no-cov"
|
49
|
+
|
50
|
+
[[tool.hatch.envs.test.matrix]]
|
51
|
+
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
52
|
+
|
53
|
+
|
54
|
+
[tool.hatch.build.targets.wheel.hooks.mypyc]
|
55
|
+
enable-by-default = false
|
56
|
+
dependencies = ["hatch-mypyc>=0.14.1"]
|
57
|
+
require-runtime-dependencies = true
|
58
|
+
mypy-args = [
|
59
|
+
"--no-warn-unused-ignores",
|
60
|
+
]
|
61
|
+
|
62
|
+
[tool.mypy]
|
63
|
+
disallow_untyped_defs = false
|
64
|
+
follow_imports = "normal"
|
65
|
+
ignore_missing_imports = true
|
66
|
+
pretty = true
|
67
|
+
show_column_numbers = true
|
68
|
+
warn_no_return = false
|
69
|
+
warn_unused_ignores = true
|
70
|
+
|
71
|
+
|
72
|
+
[tool.black]
|
73
|
+
target-version = ["py37"]
|
74
|
+
line-length = 120
|
75
|
+
skip-string-normalization = true
|
76
|
+
|
77
|
+
[tool.ruff]
|
78
|
+
target-version = "py37"
|
79
|
+
|
80
|
+
[tool.ruff.lint]
|
81
|
+
ignore = [
|
82
|
+
# Allow non-abstract empty methods in abstract base classes
|
83
|
+
"B027",
|
84
|
+
# Allow boolean positional values in function calls, like `dict.get(... True)`
|
85
|
+
"FBT003",
|
86
|
+
# Ignore checks for possible passwords
|
87
|
+
# Ignore complexity
|
88
|
+
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
|
89
|
+
"PLC1901", # empty string comparisons
|
90
|
+
"PLW2901", # `for` loop variable overwritten
|
91
|
+
"SIM114", # Combine `if` branches using logical `or` operator
|
92
|
+
]
|
93
|
+
unfixable = [
|
94
|
+
# Don't touch unused imports
|
95
|
+
"F401",
|
96
|
+
]
|
97
|
+
|
98
|
+
[tool.ruff.lint.flake8-quotes]
|
99
|
+
inline-quotes = "single"
|
100
|
+
|
101
|
+
[tool.ruff.lint.isort]
|
102
|
+
known-first-party = ["earningscall"]
|
103
|
+
|
104
|
+
|
105
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
106
|
+
ban-relative-imports = "all"
|
107
|
+
|
108
|
+
[tool.ruff.lint.per-file-ignores]
|
109
|
+
# Tests can use relative imports and assertions
|
110
|
+
"tests/**/*" = ["TID252", "S101"]
|
111
|
+
|
112
|
+
[tool.coverage.run]
|
113
|
+
source_pkgs = ["earningscall", "tests"]
|
114
|
+
branch = true
|
115
|
+
parallel = true
|
116
|
+
omit = []
|
117
|
+
|
118
|
+
[tool.coverage.paths]
|
119
|
+
earningscall = ["earningscall"]
|
120
|
+
tests = ["tests"]
|
121
|
+
|
122
|
+
[tool.coverage.report]
|
123
|
+
exclude_lines = [
|
124
|
+
"no cov",
|
125
|
+
"if __name__ == .__main__.:",
|
126
|
+
"if TYPE_CHECKING:",
|
127
|
+
]
|
@@ -26,11 +26,13 @@ def test_basic_without_conference_date():
|
|
26
26
|
|
27
27
|
def test_date_field_deserialization():
|
28
28
|
#
|
29
|
-
earnings_event = EarningsEvent.from_dict(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
earnings_event = EarningsEvent.from_dict(
|
30
|
+
{
|
31
|
+
"year": 2024,
|
32
|
+
"quarter": 1,
|
33
|
+
"conference_date": "2024-04-28T15:00:00.000-05:00",
|
34
|
+
}
|
35
|
+
)
|
34
36
|
#
|
35
37
|
assert earnings_event.year == 2024
|
36
38
|
assert earnings_event.quarter == 1
|
@@ -25,8 +25,9 @@ def test_get_demo_company():
|
|
25
25
|
company = get_company("msft")
|
26
26
|
##
|
27
27
|
transcript = company.get_transcript(year=2023, quarter=1)
|
28
|
-
assert transcript.text[:100] == (
|
29
|
-
|
28
|
+
assert transcript.text[:100] == (
|
29
|
+
'Greetings, and welcome to the Microsoft Fiscal Year 2023 First Quarter Earnings ' 'Conference Call. At '
|
30
|
+
)
|
30
31
|
|
31
32
|
|
32
33
|
@responses.activate
|
@@ -42,8 +43,9 @@ def test_get_demo_company_with_event_populated():
|
|
42
43
|
assert transcript.event.year == 2022
|
43
44
|
assert transcript.event.quarter == 1
|
44
45
|
assert transcript.event.conference_date.isoformat() == "2022-01-19T00:00:00-08:00"
|
45
|
-
assert transcript.text[:100] == (
|
46
|
-
|
46
|
+
assert transcript.text[:100] == (
|
47
|
+
"Good day and welcome to the Apple Q1 Fiscal Year 2021 earnings conference " "call. Today's call is bein"
|
48
|
+
)
|
47
49
|
|
48
50
|
|
49
51
|
# Uncomment and run following code to generate demo-symbols-v2.yaml file
|
@@ -26,12 +26,33 @@ def test_load_symbols_txt_v2():
|
|
26
26
|
def test_symbols_serialization_to_text_v2():
|
27
27
|
##
|
28
28
|
_symbols = Symbols()
|
29
|
-
_symbols.add(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
_symbols.add(
|
30
|
+
CompanyInfo(
|
31
|
+
exchange="TSX",
|
32
|
+
symbol="TLRY",
|
33
|
+
name="Tilray, Inc",
|
34
|
+
sector="Energy",
|
35
|
+
industry="Electronic Gaming & Multimedia",
|
36
|
+
)
|
37
|
+
)
|
38
|
+
_symbols.add(
|
39
|
+
CompanyInfo(
|
40
|
+
exchange="TSX",
|
41
|
+
symbol="ACB",
|
42
|
+
name="Aurora Cannabis Inc.",
|
43
|
+
sector="Technology",
|
44
|
+
industry="Electronic Gaming & Multimedia",
|
45
|
+
)
|
46
|
+
)
|
47
|
+
_symbols.add(
|
48
|
+
CompanyInfo(
|
49
|
+
exchange="NASDAQ",
|
50
|
+
symbol="HITI",
|
51
|
+
name="High Tide Inc.",
|
52
|
+
sector="Consumer Cyclical",
|
53
|
+
industry="Electronic Gaming & Multimedia",
|
54
|
+
)
|
55
|
+
)
|
35
56
|
##
|
36
57
|
result = _symbols.to_txt_v2()
|
37
58
|
##
|
@@ -1,74 +0,0 @@
|
|
1
|
-
[project]
|
2
|
-
name = "earningscall"
|
3
|
-
version = "0.0.10"
|
4
|
-
description = "The EarningsCall Python library."
|
5
|
-
readme = "README.md"
|
6
|
-
authors = [
|
7
|
-
{name = "EarningsCall", email = "dev@earningscall.biz"},
|
8
|
-
]
|
9
|
-
requires-python = ">= 3.8"
|
10
|
-
dependencies = [
|
11
|
-
"dataclasses>=0.6",
|
12
|
-
"dataclasses-json>=0.6.4",
|
13
|
-
"requests>=2.30.0",
|
14
|
-
]
|
15
|
-
license = { file = "LICENSE" }
|
16
|
-
|
17
|
-
[project.urls]
|
18
|
-
Homepage = "https://earningscall.biz"
|
19
|
-
Documentation = "https://github.com/EarningsCall/earningscall-python"
|
20
|
-
Repository = "https://github.com/EarningsCall/earningscall-python"
|
21
|
-
Issues = "https://github.com/EarningsCall/earningscall-python/issues"
|
22
|
-
Source = "https://github.com/EarningsCall/earningscall-python"
|
23
|
-
Changelog = "https://github.com/EarningsCall/earningscall-python/blob/master/CHANGELOG.md"
|
24
|
-
|
25
|
-
[build-system]
|
26
|
-
requires = ["hatchling"]
|
27
|
-
build-backend = "hatchling.build"
|
28
|
-
|
29
|
-
[tool.rye]
|
30
|
-
managed = true
|
31
|
-
dev-dependencies = [
|
32
|
-
"pytest>=8.2.1",
|
33
|
-
"responses>=0.25.0",
|
34
|
-
]
|
35
|
-
|
36
|
-
[tool.hatch.metadata]
|
37
|
-
allow-direct-references = true
|
38
|
-
|
39
|
-
|
40
|
-
[tool.hatch.build.targets.wheel]
|
41
|
-
packages = ["earningscall"]
|
42
|
-
|
43
|
-
#[tool.hatch.envs.test]
|
44
|
-
#dependencies = [
|
45
|
-
# "pytest",
|
46
|
-
# "pytest-cov",
|
47
|
-
# "responses"
|
48
|
-
#]
|
49
|
-
|
50
|
-
[[tool.hatch.envs.test.matrix]]
|
51
|
-
python = ["3.10", "3.11"]
|
52
|
-
|
53
|
-
[tool.hatch.envs.types]
|
54
|
-
extra-dependencies = [
|
55
|
-
"mypy>=1.0.0",
|
56
|
-
"responses"
|
57
|
-
]
|
58
|
-
|
59
|
-
[tool.coverage.run]
|
60
|
-
source_pkgs = ["earningscall", "tests"]
|
61
|
-
branch = true
|
62
|
-
parallel = true
|
63
|
-
omit = []
|
64
|
-
|
65
|
-
[tool.coverage.paths]
|
66
|
-
earningscall = ["earningscall"]
|
67
|
-
tests = ["tests"]
|
68
|
-
|
69
|
-
[tool.coverage.report]
|
70
|
-
exclude_lines = [
|
71
|
-
"no cov",
|
72
|
-
"if __name__ == .__main__.:",
|
73
|
-
"if TYPE_CHECKING:",
|
74
|
-
]
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# generated by rye
|
2
|
-
# use `rye lock` or `rye sync` to update this lockfile
|
3
|
-
#
|
4
|
-
# last locked with the following flags:
|
5
|
-
# pre: false
|
6
|
-
# features: []
|
7
|
-
# all-features: false
|
8
|
-
# with-sources: false
|
9
|
-
# generate-hashes: false
|
10
|
-
|
11
|
-
-e file:.
|
12
|
-
certifi==2024.2.2
|
13
|
-
# via requests
|
14
|
-
charset-normalizer==3.3.2
|
15
|
-
# via requests
|
16
|
-
dataclasses==0.6
|
17
|
-
# via earningscall
|
18
|
-
dataclasses-json==0.6.6
|
19
|
-
# via earningscall
|
20
|
-
idna==3.7
|
21
|
-
# via requests
|
22
|
-
iniconfig==2.0.0
|
23
|
-
# via pytest
|
24
|
-
marshmallow==3.21.2
|
25
|
-
# via dataclasses-json
|
26
|
-
mypy-extensions==1.0.0
|
27
|
-
# via typing-inspect
|
28
|
-
packaging==24.0
|
29
|
-
# via marshmallow
|
30
|
-
# via pytest
|
31
|
-
pluggy==1.5.0
|
32
|
-
# via pytest
|
33
|
-
pytest==8.2.1
|
34
|
-
pyyaml==6.0.1
|
35
|
-
# via responses
|
36
|
-
requests==2.32.2
|
37
|
-
# via earningscall
|
38
|
-
# via responses
|
39
|
-
responses==0.25.0
|
40
|
-
typing-extensions==4.12.0
|
41
|
-
# via typing-inspect
|
42
|
-
typing-inspect==0.9.0
|
43
|
-
# via dataclasses-json
|
44
|
-
urllib3==2.2.1
|
45
|
-
# via requests
|
46
|
-
# via responses
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# generated by rye
|
2
|
-
# use `rye lock` or `rye sync` to update this lockfile
|
3
|
-
#
|
4
|
-
# last locked with the following flags:
|
5
|
-
# pre: false
|
6
|
-
# features: []
|
7
|
-
# all-features: false
|
8
|
-
# with-sources: false
|
9
|
-
# generate-hashes: false
|
10
|
-
|
11
|
-
-e file:.
|
12
|
-
certifi==2024.2.2
|
13
|
-
# via requests
|
14
|
-
charset-normalizer==3.3.2
|
15
|
-
# via requests
|
16
|
-
dataclasses==0.6
|
17
|
-
# via earningscall
|
18
|
-
dataclasses-json==0.6.6
|
19
|
-
# via earningscall
|
20
|
-
idna==3.7
|
21
|
-
# via requests
|
22
|
-
marshmallow==3.21.2
|
23
|
-
# via dataclasses-json
|
24
|
-
mypy-extensions==1.0.0
|
25
|
-
# via typing-inspect
|
26
|
-
packaging==24.0
|
27
|
-
# via marshmallow
|
28
|
-
requests==2.32.2
|
29
|
-
# via earningscall
|
30
|
-
typing-extensions==4.12.0
|
31
|
-
# via typing-inspect
|
32
|
-
typing-inspect==0.9.0
|
33
|
-
# via dataclasses-json
|
34
|
-
urllib3==2.2.1
|
35
|
-
# via 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
|