Motleyfool
motleyfool ¶
__all__ = ['MotleyFoolClient', 'MotleyFoolRecord'] module-attribute ¶
MotleyFoolClient(timeout: float = 30.0, mode: MotleyFoolMode = MotleyFoolMode.HTTPX, requestlog: bool = False) ¶
Motley-Fool HTTP client with httpx or Selenium mode as currenlty used fallback.
Source code in src/equities_classifier/clients/motleyfool/client.py
def __init__(
self,
timeout: float = 30.0,
mode: MotleyFoolMode = MotleyFoolMode.HTTPX,
requestlog: bool = False
) -> None:
"""Initialize Motley-Fool client."""
self._client: httpx.Client | uc.Chrome
self._mode = mode
if self._mode == MotleyFoolMode.HTTPX:
self._client = httpx.Client(
base_url=self._BASE_URL,
timeout=timeout,
follow_redirects=True,
event_hooks={"request": [log_request], "response": [log_response], } if requestlog else None
)
# determine action code for next.js
self._next_action = self._get_next_action()
elif self._mode == MotleyFoolMode.SELENIUM:
options = uc.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
try:
self._client = uc.Chrome(options=options)
except Exception as e:
print("Failed to start Chrome")
raise e
try:
config = StabilizationConfig(
timeout=10, # Max wait time (seconds)
network_idle_threshold=5, # Max pending requests (allows background traffic)
strictness='normal', # 'strict' | 'normal' | 'relaxed'
debug_mode=False # Enable logging
)
self._client = stabilize(self._client, config=config)
except StabilizationTimeout as e:
print("Failed to stabilize Chrome with 'waitless'")
diagnostics = get_diagnostics(self._client)
print_report(diagnostics) # Print detailed report
raise e
self._client.get(self._BASE_URL)
time.sleep(5) # additional wait for cookie popup (required in GitHub Action environment)
self._client.find_element(By.XPATH, "//button[@id='onetrust-accept-btn-handler']").click()
else:
msg = f"MotleyFoolClient mode '{self._mode}' not valid.)"
raise MotleyFoolResponseError(msg)
__enter__() -> Self ¶
__exit__(*_: object) -> None ¶
close() -> None ¶
read_provider_profile_data(source_identifiers: Sequence[SecurityIdentifier], raise_error: bool = False) -> list[MotleyFoolRecord] ¶
Read profile data for one or more identifiers from Motley-Fool.
Source code in src/equities_classifier/clients/motleyfool/client.py
def read_provider_profile_data(
self,
source_identifiers: Sequence[SecurityIdentifier],
raise_error: bool = False,
) -> list[MotleyFoolRecord]:
"""Read profile data for one or more identifiers from Motley-Fool."""
records: list[MotleyFoolRecord] = []
for source_identifier in source_identifiers:
if source_identifier.type != SecurityIdentifierType.TICKER:
ClientHelper.invalid_security_type(
DataSourceID.MOTLEYFOOL,
source_identifier
)
continue
if self._mode == MotleyFoolMode.HTTPX:
# determine search result and fill search_result via requests
response_data = self._execute_search_request(source_identifier)
search_results = self._parse_search_results(
source_identifier,
response_data,
raise_error,
)
else:
# determine search result and fill search_result via selenium / HTML analysis
search_results = self._get_search_results(
source_identifier,
raise_error,
)
search_result = self._select_search_result(
source_identifier,
search_results,
raise_error,
)
if search_result:
if self._mode == MotleyFoolMode.HTTPX:
html = self._execute_company_request(search_result)
else:
html = self._get_company_profile_html(search_result)
record = self._parse_record(
search_result,
html,
raise_error,
)
if record:
records.append(record)
return records
MotleyFoolRecord(name: str | None = None, ticker: str | None = None, exchange: str | None = None, home_country_code: str | None = None, sector: str | None = None, industry: str | None = None, *, identifiers: list[SecurityIdentifier] = list()) dataclass ¶
Internal representation of a single Motley-Fool mapping result.