Skip to content

Commit 8ee92b4

Browse files
committed
feat: Add top-level convenience functions for Edgar API and corresponding tests
1 parent f2e2f37 commit 8ee92b4

6 files changed

Lines changed: 423 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **edgar/\_\_init\_\_.py**: Top-level convenience functions for reduced boilerplate.
13+
- `edgar.company("AAPL")` — create a `Company` without instantiating `EdgarClient`.
14+
- `edgar.get_filings("AAPL", form="10-K")` — fetch filings in one call.
15+
- `edgar.search("revenue recognition")` — full-text search in one call.
16+
- `edgar.set_user_agent()` — set user-agent programmatically.
17+
- `SEC_EDGAR_USER_AGENT` environment variable auto-detected as fallback.
18+
- Lazy singleton `EdgarClient` created on first use and cached.
19+
- **tests/test_convenience.py**: 17 unit tests for convenience functions (`set_user_agent`, `_get_client`, `company`, `get_filings`, `search`, env var fallback, caching, exports).
20+
- **samples/use_convenience.py**: Sample file demonstrating top-level convenience functions (env var, `set_user_agent`, `company`, `get_filings`, `search`).
1221
- **edgar/models.py**: `Fact` and `Facts` XBRL dataclass models.
1322
- `Facts` wraps the deeply nested `company_facts` JSON (4 levels) with `get(taxonomy, concept, unit=None)` returning a flat `list[Fact]` sorted by end date.
1423
- `Facts.taxonomies` lists available namespaces (e.g. `['dei', 'us-gaap', 'ifrs-full']`).

edgar/__init__.py

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
"""SEC EDGAR API client library."""
1+
"""SEC EDGAR API client library.
2+
3+
Top-level convenience functions let you skip the ``EdgarClient`` boilerplate::
4+
5+
import edgar
6+
7+
company = edgar.company("AAPL")
8+
filings = edgar.get_filings("AAPL", form="10-K")
9+
results = edgar.search("revenue recognition")
10+
11+
Set the ``SEC_EDGAR_USER_AGENT`` environment variable to avoid passing
12+
``user_agent`` on every call::
13+
14+
export SEC_EDGAR_USER_AGENT="Your Name your-email@example.com"
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import os
220

321
from edgar.client import EdgarClient
422
from edgar.exceptions import EdgarError, EdgarRequestError, EdgarParseError
@@ -8,4 +26,103 @@
826
"EdgarError",
927
"EdgarRequestError",
1028
"EdgarParseError",
29+
"company",
30+
"get_filings",
31+
"search",
32+
"set_user_agent",
1133
]
34+
35+
_ENV_VAR = "SEC_EDGAR_USER_AGENT"
36+
_user_agent: str | None = None
37+
_client: EdgarClient | None = None
38+
39+
40+
def set_user_agent(user_agent: str) -> None:
41+
"""Set the default user-agent for module-level convenience functions.
42+
43+
This value takes priority over the ``SEC_EDGAR_USER_AGENT``
44+
environment variable.
45+
"""
46+
47+
global _user_agent, _client # pylint: disable=global-statement
48+
_user_agent = user_agent
49+
_client = None # force re-creation on next call
50+
51+
52+
def _get_client() -> EdgarClient:
53+
"""Return a lazily-initialised shared ``EdgarClient``.
54+
55+
Resolution order for the user-agent string:
56+
57+
1. Value set via ``set_user_agent()``.
58+
2. The ``SEC_EDGAR_USER_AGENT`` environment variable.
59+
60+
Raises ``EdgarError`` if neither is available.
61+
"""
62+
63+
global _client # pylint: disable=global-statement
64+
if _client is not None:
65+
return _client
66+
67+
agent = _user_agent or os.environ.get(_ENV_VAR)
68+
if not agent:
69+
raise EdgarError(
70+
"No user-agent configured. Either call edgar.set_user_agent() "
71+
f"or set the {_ENV_VAR} environment variable."
72+
)
73+
_client = EdgarClient(user_agent=agent)
74+
return _client
75+
76+
77+
def company(identifier: str) -> object:
78+
"""Return a ``Company`` object for the given ticker or CIK.
79+
80+
>>> import edgar
81+
>>> edgar.company("AAPL").name
82+
'Apple Inc.'
83+
"""
84+
85+
return _get_client().company(identifier)
86+
87+
88+
def get_filings(
89+
identifier: str,
90+
form: str | None = None,
91+
start: int = 0,
92+
number_of_filings: int = 100,
93+
) -> list:
94+
"""Return filings for a company as ``Filing`` model objects.
95+
96+
>>> import edgar
97+
>>> edgar.get_filings("AAPL", form="10-K")[0].form_type
98+
'10-K'
99+
"""
100+
101+
return _get_client().company(identifier).get_filings(
102+
form=form, start=start, number_of_filings=number_of_filings
103+
)
104+
105+
106+
def search(
107+
q: str,
108+
form_types: list[str] | None = None,
109+
start_date: str | None = None,
110+
end_date: str | None = None,
111+
start: int = 0,
112+
size: int = 100,
113+
) -> list:
114+
"""Full-text search across SEC EDGAR filings.
115+
116+
>>> import edgar
117+
>>> edgar.search("revenue recognition", form_types=["10-K"])[0].company_name
118+
'Apple Inc. ...'
119+
"""
120+
121+
return _get_client().search(
122+
q=q,
123+
form_types=form_types,
124+
start_date=start_date,
125+
end_date=end_date,
126+
start=start,
127+
size=size,
128+
)

samples/use_convenience.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Example usage of top-level convenience functions."""
2+
3+
import edgar
4+
5+
# ---------------------------------------------------------------------------
6+
# Option A: Set user-agent via environment variable (recommended for scripts)
7+
# ---------------------------------------------------------------------------
8+
9+
# In your shell:
10+
# export SEC_EDGAR_USER_AGENT="Your Name your-email@example.com"
11+
#
12+
# Then simply:
13+
# import edgar
14+
# company = edgar.company("AAPL")
15+
16+
# ---------------------------------------------------------------------------
17+
# Option B: Set user-agent programmatically
18+
# ---------------------------------------------------------------------------
19+
20+
edgar.set_user_agent("Your Name your-email@example.com")
21+
22+
# ---------------------------------------------------------------------------
23+
# Quick company lookup — no EdgarClient needed
24+
# ---------------------------------------------------------------------------
25+
26+
company = edgar.company("AAPL")
27+
print(company)
28+
# Output: <Company ticker='AAPL' cik='0000320193' name='Apple Inc.'>
29+
30+
# ---------------------------------------------------------------------------
31+
# Get filings directly by ticker
32+
# ---------------------------------------------------------------------------
33+
34+
filings = edgar.get_filings("AAPL", form="10-K")
35+
print(f"Found {len(filings)} 10-K filings")
36+
print(filings[0])
37+
# Output: Filing(form_type='10-K', filing_date='...', accession_number='...')
38+
39+
# ---------------------------------------------------------------------------
40+
# Full-text search — one line
41+
# ---------------------------------------------------------------------------
42+
43+
results = edgar.search("revenue recognition", form_types=["10-K"], size=5)
44+
print(f"Found {len(results)} search results")
45+
for result in results[:3]:
46+
print(f" {result.company_name}{result.form} ({result.filing_date})")
47+
48+
# ---------------------------------------------------------------------------
49+
# All original EdgarClient functionality still works
50+
# ---------------------------------------------------------------------------
51+
52+
client = edgar.EdgarClient(user_agent="Your Name your-email@example.com")
53+
company = client.company("MSFT")
54+
print(company.name)
55+
# Output: MICROSOFT CORP

0 commit comments

Comments
 (0)