Files
edgartools/venv/lib/python3.10/site-packages/edgar/__init__.py
2025-12-09 12:13:01 +01:00

189 lines
6.9 KiB
Python

# SPDX-FileCopyrightText: 2022-present Dwight Gunning <dgunning@gmail.com>
#
# SPDX-License-Identifier: MIT
import re
from functools import lru_cache, partial
from typing import List, Optional, Union
from edgar._filings import Attachment, Attachments, Filing, FilingHeader, FilingHomepage, Filings, get_by_accession_number, get_by_accession_number_enriched, get_filings
from edgar.core import CAUTION, CRAWL, NORMAL, edgar_mode, get_identity, listify, set_identity
from edgar.current_filings import CurrentFilings, get_all_current_filings, get_current_filings, iter_current_filings_pages
from edgar.entity import (
Company,
CompanyData,
CompanyFiling,
CompanyFilings,
CompanySearchResults,
Entity,
EntityData,
find_company,
get_cik_lookup_data,
get_company_facts,
get_company_tickers,
get_entity,
get_entity_submissions,
get_icon_from_ticker,
get_ticker_to_cik_lookup,
)
from edgar.files import detect_page_breaks, mark_page_breaks
from edgar.files.html import Document
from edgar.financials import Financials, MultiFinancials
from edgar.funds import FundClass, FundCompany, FundSeries, find_fund
from edgar.funds.reports import NPORT_FORMS, FundReport
from edgar.storage import download_edgar_data, download_filings, is_using_local_storage, set_local_storage_path, use_local_storage
from edgar.storage_management import (
StorageAnalysis,
StorageInfo,
analyze_storage,
availability_summary,
check_filing,
check_filings_batch,
cleanup_storage,
clear_cache,
optimize_storage,
storage_info,
)
from edgar.thirteenf import THIRTEENF_FORMS, ThirteenF
from edgar.xbrl import XBRL
# Fix for Issue #457: Clear locale-corrupted cache files on first import
# This is a one-time operation that only runs if the marker file doesn't exist
try:
from edgar.httpclient import clear_locale_corrupted_cache
clear_locale_corrupted_cache()
except Exception:
# Silently continue if cache clearing fails - it's not critical
pass
# Another name for get_current_filings
get_latest_filings = get_current_filings
latest_filings = get_current_filings
current_filings = get_current_filings
# Fund portfolio report filings
get_fund_portfolio_filings = partial(get_filings, form=NPORT_FORMS)
# Restricted stock sales
get_restricted_stock_filings = partial(get_filings, form=[144])
# Insider transaction filings
get_insider_transaction_filings = partial(get_filings, form=[3, 4, 5])
# 13F filings - portfolio holdings
get_portfolio_holding_filings = partial(get_filings, form=THIRTEENF_FORMS)
@lru_cache(maxsize=16)
def find(search_id: Union[str, int]) -> Optional[Union[Filing, Entity, CompanySearchResults, FundCompany, FundClass, FundSeries]]:
"""This is an uber search function that can take a variety of search ids and return the appropriate object
- accession number -> returns a Filing
- CIK -> returns an Entity
- Class/Contract ID -> returns a FundClass
- Series ID -> returns a FundSeries
- Ticker -> returns a Company or a Fund if the ticker is a fund ticker
- Company name -> returns CompanySearchResults
:type: object
"""
if isinstance(search_id, int):
return Entity(search_id)
elif re.match(r"\d{10}-\d{2}-\d{6}", search_id):
return get_by_accession_number_enriched(search_id)
elif re.match(r"^\d{18}$", search_id): # accession number with no dashes
accession_number = search_id[:10] + "-" + search_id[10:12] + "-" + search_id[12:]
return get_by_accession_number_enriched(accession_number)
elif re.match(r"\d{4,10}$", search_id):
return Entity(search_id)
elif re.match(r"^[A-WYZ]{1,5}([.-][A-Z])?$", search_id): # Ticker (including dot or hyphenated)
return Entity(search_id)
elif re.match(r"^[A-Z]{4}X$", search_id): # Mutual Fund Ticker
return find_fund(search_id)
elif re.match(r"^[CS]\d+$", search_id):
return find_fund(search_id)
elif re.match(r"^\d{6,}-", search_id):
# Probably an invalid accession number
return None
else:
return find_company(search_id)
def matches_form(sec_filing: Filing,
form: Union[str, List[str]]) -> bool:
"""Check if the filing matches the forms"""
form_list = listify(form)
if sec_filing.form in form_list + [f"{f}/A" for f in form_list]:
return True
return False
class DataObjectException(Exception):
def __init__(self, filing: Filing):
self.message = f"Could not create a data object for Form {filing.form} filing: {filing.accession_no}"
super().__init__(self.message)
def obj(sec_filing: Filing) -> Optional[object]:
"""
Depending on the filing return the data object that contains the data for the filing
This usually coms from the xml associated with the filing, but it can also come from the extracted xbrl
:param sec_filing: The filing
:return:
"""
from edgar.company_reports import CurrentReport, EightK, TenK, TenQ, TwentyF
from edgar.effect import Effect
from edgar.form144 import Form144
from edgar.muniadvisors import MunicipalAdvisorForm
from edgar.offerings import FormC, FormD
from edgar.ownership import Form3, Form4, Form5, Ownership
if matches_form(sec_filing, "6-K"):
return CurrentReport(sec_filing)
if matches_form(sec_filing, "8-K"):
return EightK(sec_filing)
elif matches_form(sec_filing, "10-Q"):
return TenQ(sec_filing)
elif matches_form(sec_filing, "10-K"):
return TenK(sec_filing)
elif matches_form(sec_filing, "20-F"):
return TwentyF(sec_filing)
elif matches_form(sec_filing, THIRTEENF_FORMS):
# ThirteenF can work with either XML (2013+) or TXT (2012 and earlier) format
return ThirteenF(sec_filing)
elif matches_form(sec_filing, "144"):
return Form144.from_filing(sec_filing)
elif matches_form(sec_filing, "MA-I"):
return MunicipalAdvisorForm.from_filing(sec_filing)
elif matches_form(sec_filing, "3"):
xml = sec_filing.xml()
if xml:
return Form3(**Ownership.parse_xml(xml))
elif matches_form(sec_filing, "4"):
xml = sec_filing.xml()
if xml:
return Form4(**Ownership.parse_xml(xml))
elif matches_form(sec_filing, "5"):
xml = sec_filing.xml()
if xml:
return Form5(**Ownership.parse_xml(xml))
elif matches_form(sec_filing, "EFFECT"):
xml = sec_filing.xml()
if xml:
return Effect.from_xml(xml)
elif matches_form(sec_filing, "D"):
xml = sec_filing.xml()
if xml:
return FormD.from_xml(xml)
elif matches_form(sec_filing, ["C", "C-U", "C-AR", "C-TR"]):
xml = sec_filing.xml()
if xml:
return FormC.from_xml(xml, form=sec_filing.form)
elif matches_form(sec_filing, ["NPORT-P", "NPORT-EX"]):
return FundReport.from_filing(sec_filing)
filing_xbrl = sec_filing.xbrl()
if filing_xbrl:
return filing_xbrl