From d6832a346fb7783e5291e00cb09867c8a5adc3eb Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 16:34:40 -0400 Subject: [PATCH 01/17] fmp date timezone fixes --- .../provider/standard_models/equity_quote.py | 2 +- .../standard_models/etf_equity_exposure.py | 2 +- .../standard_models/eu_yield_curve.py | 2 +- .../standard_models/treasury_rates.py | 26 +++--- .../openbb_benzinga/models/analyst_search.py | 38 ++++---- .../openbb_benzinga/models/price_target.py | 25 ++++-- .../openbb_fmp/models/equity_historical.py | 10 ++- .../fmp/openbb_fmp/models/etf_holdings.py | 2 +- .../fmp/openbb_fmp/models/etf_info.py | 2 +- .../fmp/openbb_fmp/models/financial_ratios.py | 90 ++++++++++++------- .../fmp/openbb_fmp/models/index_historical.py | 9 +- .../fmp/openbb_fmp/models/key_metrics.py | 5 +- .../fmp/openbb_fmp/models/market_indices.py | 9 +- .../providers/fmp/openbb_fmp/utils/helpers.py | 31 ++++++- .../fred/openbb_fred/models/regional.py | 2 +- .../models/equity_historical.py | 2 +- 16 files changed, 170 insertions(+), 87 deletions(-) diff --git a/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py b/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py index c98c7cfdfad8..fe8885d89dd1 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py @@ -142,7 +142,7 @@ class EquityQuoteData(Data): change_percent: Optional[float] = Field( default=None, description="Change in price as a normalized percentage.", - json_schema_extra={"x-frontendmultiply": 100}, + json_schema_extra={"x-frontend_multiply": 100}, ) year_high: Optional[float] = Field( default=None, description="The one year high (52W High)." diff --git a/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py b/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py index eb739ea1122e..b2ceb5f02ea9 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/etf_equity_exposure.py @@ -35,7 +35,7 @@ class EtfEquityExposureData(Data): weight: Optional[float] = Field( default=None, description="The weight of the equity in the ETF, as a normalized percent.", - json_schema_extra={"units_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) market_value: Optional[Union[int, float]] = Field( default=None, diff --git a/openbb_platform/core/openbb_core/provider/standard_models/eu_yield_curve.py b/openbb_platform/core/openbb_core/provider/standard_models/eu_yield_curve.py index 81d01c278b80..eaf9efd68779 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/eu_yield_curve.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/eu_yield_curve.py @@ -25,5 +25,5 @@ class EUYieldCurveData(Data): rate: Optional[float] = Field( description="Yield curve rate, as a normalized percent.", default=None, - json_schema_extra={"unit_measurement": "percent.", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) diff --git a/openbb_platform/core/openbb_core/provider/standard_models/treasury_rates.py b/openbb_platform/core/openbb_core/provider/standard_models/treasury_rates.py index 743ec65a746c..93149d1c3776 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/treasury_rates.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/treasury_rates.py @@ -33,65 +33,65 @@ class TreasuryRatesData(Data): week_4: Optional[float] = Field( default=None, description="4 week Treasury bills rate (secondary market).", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) month_1: Optional[float] = Field( description="1 month Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) month_2: Optional[float] = Field( description="2 month Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) month_3: Optional[float] = Field( description="3 month Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) month_6: Optional[float] = Field( description="6 month Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_1: Optional[float] = Field( description="1 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_2: Optional[float] = Field( description="2 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_3: Optional[float] = Field( description="3 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_5: Optional[float] = Field( description="5 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_7: Optional[float] = Field( description="7 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_10: Optional[float] = Field( description="10 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_20: Optional[float] = Field( description="20 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) year_30: Optional[float] = Field( description="30 year Treasury rate.", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) diff --git a/openbb_platform/providers/benzinga/openbb_benzinga/models/analyst_search.py b/openbb_platform/providers/benzinga/openbb_benzinga/models/analyst_search.py index f5cf40a2a5a8..3c9cf4646ab8 100644 --- a/openbb_platform/providers/benzinga/openbb_benzinga/models/analyst_search.py +++ b/openbb_platform/providers/benzinga/openbb_benzinga/models/analyst_search.py @@ -90,19 +90,19 @@ class BenzingaAnalystSearchData(AnalystSearchData): overall_success_rate: Optional[float] = Field( default=None, description="The percentage (normalized) of gain/loss ratings that resulted in a gain overall.", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) overall_avg_return_percentile: Optional[float] = Field( default=None, description="The percentile (normalized) of this analyst's overall average" + " return per rating in comparison to other analysts' overall average returns per rating.", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) total_ratings_percentile: Optional[float] = Field( default=None, description="The percentile (normalized) of this analyst's total number of ratings" + " in comparison to the total number of ratings published by all other analysts", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) total_ratings: Optional[int] = Field( default=None, @@ -119,13 +119,13 @@ class BenzingaAnalystSearchData(AnalystSearchData): overall_average_return: Optional[float] = Field( default=None, description="The average percent (normalized) price difference per rating since the date of recommendation", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) overall_std_dev: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings since the date of recommendation", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="overall_stdev", ) gain_count_1m: Optional[int] = Field( @@ -141,14 +141,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): average_return_1m: Optional[float] = Field( default=None, description="The average percent (normalized) price difference per rating over the last month", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="1m_average_return", ) std_dev_1m: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last month", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="1m_stdev", ) smart_score_1m: Optional[float] = Field( @@ -176,14 +176,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): default=None, description="The average percent (normalized) price difference per rating over" + " the last 3 months", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="3m_average_return", ) std_dev_3m: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last 3 months", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="3m_stdev", ) smart_score_3m: Optional[float] = Field( @@ -211,14 +211,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): default=None, description="The average percent (normalized) price difference per rating over" + " the last 6 months", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="6m_average_return", ) std_dev_6m: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last 6 months", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="6m_stdev", ) gain_count_9m: Optional[int] = Field( @@ -235,14 +235,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): default=None, description="The average percent (normalized) price difference per rating over" + " the last 9 months", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="9m_average_return", ) std_dev_9m: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last 9 months", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="9m_stdev", ) smart_score_9m: Optional[float] = Field( @@ -270,14 +270,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): default=None, description="The average percent (normalized) price difference per rating over" + " the last 1 year", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="1y_average_return", ) std_dev_1y: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last 1 year", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="1y_stdev", ) smart_score_1y: Optional[float] = Field( @@ -305,14 +305,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): default=None, description="The average percent (normalized) price difference per rating over" + " the last 2 years", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="2y_average_return", ) std_dev_2y: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last 2 years", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="2y_stdev", ) smart_score_2y: Optional[float] = Field( @@ -340,14 +340,14 @@ class BenzingaAnalystSearchData(AnalystSearchData): default=None, description="The average percent (normalized) price difference per rating over" + " the last 3 years", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="3y_average_return", ) std_dev_3y: Optional[float] = Field( default=None, description="The standard deviation in percent (normalized) price difference in the" + " analyst's ratings over the last 3 years", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, alias="3y_stdev", ) smart_score_3y: Optional[float] = Field( diff --git a/openbb_platform/providers/benzinga/openbb_benzinga/models/price_target.py b/openbb_platform/providers/benzinga/openbb_benzinga/models/price_target.py index d799e3410d88..b0517af51279 100644 --- a/openbb_platform/providers/benzinga/openbb_benzinga/models/price_target.py +++ b/openbb_platform/providers/benzinga/openbb_benzinga/models/price_target.py @@ -45,11 +45,26 @@ class BenzingaPriceTargetQueryParams(PriceTargetQueryParams): Source: https://docs.benzinga.io/benzinga-apis/calendar/get-ratings """ + # using __alias_dict__ for the aliasing of the fields instead of `Field` alias kwarg + # otherwise openapi schema shows as `parameters[date_from]` instead of `start_date` __alias_dict__ = { "limit": "pagesize", "symbol": "parameters[tickers]", + "date": "parameters[date]", + "start_date": "parameters[date_from]", + "end_date": "parameters[date_to]", + "updated": "parameters[updated]", + "importance": "parameters[importance]", + "action": "parameters[action]", + "analyst_ids": "parameters[analyst_id]", + "firm_ids": "parameters[firm_id]", + } + __json_schema_extra__ = { + "symbol": ["multiple_items_allowed"], + "analyst_ids": ["multiple_items_allowed"], + "firm_ids": ["multiple_items_allowed"], + "fields": ["multiple_items_allowed"], } - __json_schema_extra__ = {"symbol": ["multiple_items_allowed"]} page: Optional[int] = Field( default=0, @@ -60,17 +75,14 @@ class BenzingaPriceTargetQueryParams(PriceTargetQueryParams): date: Optional[dateType] = Field( default=None, description="Date for calendar data, shorthand for date_from and date_to.", - alias="parameters[date]", ) start_date: Optional[dateType] = Field( default=None, description=QUERY_DESCRIPTIONS.get("start_date", ""), - alias="parameters[date_from]", ) end_date: Optional[dateType] = Field( default=None, description=QUERY_DESCRIPTIONS.get("end_date", ""), - alias="parameters[date_to]", ) updated: Optional[Union[dateType, int]] = Field( default=None, @@ -78,13 +90,11 @@ class BenzingaPriceTargetQueryParams(PriceTargetQueryParams): + " This will force the sort order to be Greater Than or Equal to the timestamp indicated." + " The date can be a date string or a Unix timestamp." + " The date string must be in the format of YYYY-MM-DD.", - alias="parameters[updated]", ) importance: Optional[int] = Field( default=None, description="Importance level to filter by." + " Uses Greater Than or Equal To the importance indicated", - alias="parameters[importance]", ) action: Optional[ Literal[ @@ -103,18 +113,15 @@ class BenzingaPriceTargetQueryParams(PriceTargetQueryParams): ] = Field( default=None, description="Filter by a specific action_company.", - alias="parameters[action]", ) analyst_ids: Optional[Union[List[str], str]] = Field( default=None, description="Comma-separated list of analyst (person) IDs." + " Omitting will bring back all available analysts.", - alias="parameters[analyst_id]", ) firm_ids: Optional[Union[List[str], str]] = Field( default=None, description="Comma-separated list of firm IDs.", - alias="parameters[firm_id]", ) fields: Optional[Union[List[str], str]] = Field( default=None, diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py b/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py index 5225a0daa183..19ead52f7729 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py @@ -22,8 +22,8 @@ amake_request, get_querystring, ) -from openbb_fmp.utils.helpers import get_interval -from pydantic import Field +from openbb_fmp.utils.helpers import get_interval, parse_date +from pydantic import Field, field_validator class FMPEquityHistoricalQueryParams(EquityHistoricalQueryParams): @@ -43,6 +43,7 @@ class FMPEquityHistoricalQueryParams(EquityHistoricalQueryParams): class FMPEquityHistoricalData(EquityHistoricalData): """FMP Equity Historical Price Data.""" + symbol: Optional[str] = Field(default=None, description="Symbol of the equity.") adj_close: Optional[float] = Field( default=None, description=DATA_DESCRIPTIONS.get("adj_close", "") ) @@ -60,6 +61,11 @@ class FMPEquityHistoricalData(EquityHistoricalData): json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) + @field_validator("date", mode="before", check_fields=False) + def date_validate(cls, v): # pylint: disable=E0213 + """Return formatted datetime.""" + return parse_date(v, "America/New_York") + class FMPEquityHistoricalFetcher( Fetcher[ diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py index 6c6566828dee..385a24039262 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py @@ -73,7 +73,7 @@ class FMPEtfHoldingsData(EtfHoldingsData): description="The weight of the holding, as a normalized percent.", alias="pctVal", default=None, - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) payoff_profile: Optional[str] = Field( description="The payoff profile of the holding.", diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py index 21cfab00a8aa..680c12866991 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py @@ -43,7 +43,7 @@ class FMPEtfInfoData(EtfInfoData): expense_ratio: Optional[float] = Field( default=None, description="The expense ratio, as a normalized percent.", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) holdings_count: Optional[int] = Field( default=None, description="Number of holdings." diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index 2cc5b5f7d9b9..9ccd04d97099 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -8,13 +8,16 @@ FinancialRatiosData, FinancialRatiosQueryParams, ) -from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS -from openbb_core.provider.utils.errors import EmptyDataError +from openbb_core.provider.utils.descriptions import ( + DATA_DESCRIPTIONS, + QUERY_DESCRIPTIONS, +) from openbb_core.provider.utils.helpers import ( - amake_request, + ClientResponse, + ClientSession, + amake_requests, to_snake_case, ) -from openbb_fmp.utils.helpers import response_callback from pydantic import Field, model_validator @@ -24,22 +27,30 @@ class FMPFinancialRatiosQueryParams(FinancialRatiosQueryParams): Source: https://financialmodelingprep.com/developer/docs/#Company-Financial-Ratios """ + __json_schema_extra__ = {"symbol": ["multiple_items_allowed"]} + period: Literal["annual", "quarter", "ttm"] = Field( default="annual", description=QUERY_DESCRIPTIONS.get("period", "") ) + with_ttm: Optional[bool] = Field( + default=False, description="Include trailing twelve months (TTM) data." + ) class FMPFinancialRatiosData(FinancialRatiosData): """FMP Financial Ratios Data.""" __alias_dict__ = { - "dividend_yield_ttm": "dividend_yiel_ttm", - "dividend_yield_ttm_percent": "dividend_yiel_percentage_ttm", + "dividend_yield": "dividend_yiel", + "dividend_yield_percentage": "dividend_yiel_percentage", "period_ending": "date", "fiscal_period": "period", "fiscal_year": "calendar_year", } + symbol: Optional[str] = Field( + default=None, description=DATA_DESCRIPTIONS.get("symbol", "") + ) current_ratio: Optional[float] = Field(default=None, description="Current ratio.") quick_ratio: Optional[float] = Field(default=None, description="Quick ratio.") cash_ratio: Optional[float] = Field(default=None, description="Cash ratio.") @@ -226,37 +237,54 @@ async def aextract_data( base_url = "https://financialmodelingprep.com/api/v3" - ttm_url = f"{base_url}/ratios-ttm/{query.symbol}?&apikey={api_key}" + ttm_dict = {"period": "TTM", "date": datetime.now().strftime("%Y-%m-%d")} - url = ( - f"{base_url}/ratios/{query.symbol}?" - f"period={query.period}&limit={query.limit}&apikey={api_key}" - if query.period != "ttm" - else ttm_url - ) - results = await amake_request( - url, response_callback=response_callback, **kwargs - ) + async def response_callback( + response: ClientResponse, session: ClientSession + ) -> List[Dict]: + results = await response.json() + symbol = response.url.parts[-1] + + # TTM data + ttm_url = f"{base_url}/ratios-ttm/{symbol}?&apikey={api_key}" + if query.with_ttm and (ratios_ttm := await session.get_one(ttm_url)): + results.insert( + 0, + {"symbol": symbol, **ttm_dict, **ratios_ttm}, + ) + + if query.period == "ttm": + results = [{**ttm_dict, **item} for item in results] + + return results - if not results: - raise EmptyDataError(f"No data found for the symbol {query.symbol}.") + endpoint = "ratios" if query.period != "ttm" else "ratios-ttm" - return results # type: ignore + urls = [ + (f"{base_url}/{endpoint}/{symbol}") for symbol in query.symbol.split(",") + ] + + kwargs.update( + params={"period": query.period, "limit": query.limit, "apikey": api_key} + ) + + return await amake_requests(urls, response_callback=response_callback, **kwargs) @staticmethod def transform_data( query: FMPFinancialRatiosQueryParams, data: List[Dict], **kwargs: Any ) -> List[FMPFinancialRatiosData]: """Return the transformed data.""" - results = [ - {to_snake_case(k).replace("ttm", ""): v for k, v in item.items()} - for item in data - ] - if query.period == "ttm": - results[0].update( - {"period": "TTM", "date": datetime.now().date().strftime("%Y-%m-%d")} - ) - for item in results: - item.pop("symbol", None) - item.pop("dividend_yiel_percentage", None) - return [FMPFinancialRatiosData.model_validate(d) for d in results] + results = [] + for item in data: + new_item = {to_snake_case(k).replace("ttm", ""): v for k, v in item.items()} + + if new_item.get("period", None) != "TTM": + new_item.pop("dividend_yiel_percentage", None) + + if len(query.symbol.split(",")) == 1: + new_item.pop("symbol", None) + + results.append(FMPFinancialRatiosData.model_validate(new_item)) + + return results diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py b/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py index 33eabd6f5b3f..ab248116a2a2 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py @@ -22,8 +22,8 @@ amake_request, get_querystring, ) -from openbb_fmp.utils.helpers import get_interval -from pydantic import Field +from openbb_fmp.utils.helpers import get_interval, parse_date +from pydantic import Field, field_validator class FMPIndexHistoricalQueryParams(IndexHistoricalQueryParams): @@ -57,6 +57,11 @@ class FMPIndexHistoricalData(IndexHistoricalData): json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) + @field_validator("date", mode="before", check_fields=False) + def date_validate(cls, v): # pylint: disable=E0213 + """Return formatted datetime.""" + return parse_date(v, "America/New_York") + class FMPIndexHistoricalFetcher( Fetcher[ diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py b/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py index 3f7f7b0ce3f9..f88edcebffcf 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py @@ -122,7 +122,7 @@ class FMPKeyMetricsData(KeyMetricsData): dividend_yield: Optional[float] = Field( default=None, description="Dividend yield, as a normalized percent.", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) payout_ratio: Optional[float] = Field(default=None, description="Payout ratio") sales_general_and_administrative_to_revenue: Optional[float] = Field( @@ -251,6 +251,9 @@ async def get_one(symbol): ttm_url, response_callback=response_callback, **kwargs ) ): + if isinstance(metrics_ttm, list): + metrics_ttm = metrics_ttm[0] + result.insert( # type: ignore 0, { diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py b/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py index 15e4bb6719c5..e90e7777c921 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py @@ -13,8 +13,8 @@ ) from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS from openbb_core.provider.utils.helpers import get_querystring -from openbb_fmp.utils.helpers import get_data_many -from pydantic import Field, NonNegativeInt +from openbb_fmp.utils.helpers import get_data_many, parse_date +from pydantic import Field, NonNegativeInt, field_validator class FMPMarketIndicesQueryParams(MarketIndicesQueryParams): @@ -63,6 +63,11 @@ class FMPMarketIndicesData(MarketIndicesData): default=None, ) + @field_validator("date", mode="before", check_fields=False) + def date_validate(cls, v): # pylint: disable=E0213 + """Return formatted datetime.""" + return parse_date(v, "America/New_York") + class FMPMarketIndicesFetcher( Fetcher[ diff --git a/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py b/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py index c37c5718cdc6..5634579eef8c 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py +++ b/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py @@ -1,8 +1,12 @@ """FMP Helpers Module.""" -from datetime import date as dateType +from datetime import ( + date as dateType, + datetime, +) from typing import Any, Dict, List, Optional, Union +from dateutil import parser from openbb_core.provider.utils.client import ClientSession from openbb_core.provider.utils.errors import EmptyDataError from openbb_core.provider.utils.helpers import ( @@ -12,6 +16,7 @@ get_querystring, ) from pydantic import BaseModel +from pytz import timezone async def response_callback( @@ -144,3 +149,27 @@ def get_interval(value: str) -> str: } return f"{value[:-1]}{intervals[value[-1]]}" + + +# some fmp endpoint return date in EST without a timezone, this function will parse it +# and return a datetime object with the correct timezone +def parse_date(date: str, with_tz: Optional[str] = None) -> datetime: + """Parse the date string. + + Parameters + ---------- + date: str + The date string to parse. + with_tz: Optional[str] + Timezone the date is in. + + Returns + ------- + datetime + The parsed datetime. + """ + + date_dt = parser.isoparse(str(date)) + local = timezone(with_tz or "America/New_York") + + return local.localize(date_dt.replace(tzinfo=None)) if with_tz else date_dt diff --git a/openbb_platform/providers/fred/openbb_fred/models/regional.py b/openbb_platform/providers/fred/openbb_fred/models/regional.py index 64d8b12cc6ff..79a08d4b7612 100644 --- a/openbb_platform/providers/fred/openbb_fred/models/regional.py +++ b/openbb_platform/providers/fred/openbb_fred/models/regional.py @@ -182,7 +182,7 @@ class FredRegionalData(SeriesData): ) value: Optional[Union[int, float]] = Field( default=None, - description="The obersvation value. The units are defined in the search results by series ID.", + description="The observation value. The units are defined in the search results by series ID.", ) series_id: str = Field( description="The individual series ID for the region.", diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py index ff5f08932583..99e80b44376d 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py @@ -96,7 +96,7 @@ class IntrinioEquityHistoricalData(EquityHistoricalData): default=None, description="Percent change in the price of the symbol from the previous day.", alias="percent_change", - json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100}, + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) adj_open: Optional[float] = Field( default=None, From 405d4f59851182a5c35e837c8679b06fac021fd4 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 16:47:11 -0400 Subject: [PATCH 02/17] pylint --- .../openbb_core/provider/standard_models/index_historical.py | 2 +- .../providers/fmp/openbb_fmp/models/index_historical.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openbb_platform/core/openbb_core/provider/standard_models/index_historical.py b/openbb_platform/core/openbb_core/provider/standard_models/index_historical.py index ad5c1ecea46b..cd32f05e594c 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/index_historical.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/index_historical.py @@ -69,7 +69,7 @@ class IndexHistoricalData(Data): @field_validator("date", mode="before", check_fields=False) @classmethod - def date_validate(cls, v): + def date_validate(cls, v): # pylint: disable=E0213 """Return formatted datetime.""" if ":" in str(v): return parser.isoparse(str(v)) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py b/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py index ab248116a2a2..879d0b500dc1 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py @@ -58,6 +58,7 @@ class FMPIndexHistoricalData(IndexHistoricalData): ) @field_validator("date", mode="before", check_fields=False) + @classmethod def date_validate(cls, v): # pylint: disable=E0213 """Return formatted datetime.""" return parse_date(v, "America/New_York") From 0da86a3f831a42d695d59a0aac67b20454cf3afe Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 16:58:04 -0400 Subject: [PATCH 03/17] types --- .../providers/fmp/openbb_fmp/models/etf_info.py | 2 +- .../fmp/openbb_fmp/models/financial_ratios.py | 14 +++++++------- .../openbb_intrinio/models/equity_historical.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py index 680c12866991..4df138fc01a1 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_info.py @@ -76,7 +76,7 @@ async def aextract_data( """Return the raw data from the FMP endpoint.""" api_key = credentials.get("fmp_api_key") if credentials else "" symbols = query.symbol.split(",") - results = [] + results: List[dict] = [] async def get_one(symbol): """Get one symbol.""" diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index 9ccd04d97099..4b4a7498e48f 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -242,20 +242,20 @@ async def aextract_data( async def response_callback( response: ClientResponse, session: ClientSession ) -> List[Dict]: - results = await response.json() + results: List[dict] = await response.json() symbol = response.url.parts[-1] # TTM data ttm_url = f"{base_url}/ratios-ttm/{symbol}?&apikey={api_key}" - if query.with_ttm and (ratios_ttm := await session.get_one(ttm_url)): + + if query.period == "ttm": + results = [{**ttm_dict, **item} for item in results] + elif query.with_ttm and (ratios_ttm := await session.get_one(ttm_url)): results.insert( 0, {"symbol": symbol, **ttm_dict, **ratios_ttm}, ) - if query.period == "ttm": - results = [{**ttm_dict, **item} for item in results] - return results endpoint = "ratios" if query.period != "ttm" else "ratios-ttm" @@ -275,11 +275,11 @@ def transform_data( query: FMPFinancialRatiosQueryParams, data: List[Dict], **kwargs: Any ) -> List[FMPFinancialRatiosData]: """Return the transformed data.""" - results = [] + results: List[FMPFinancialRatiosData] = [] for item in data: new_item = {to_snake_case(k).replace("ttm", ""): v for k, v in item.items()} - if new_item.get("period", None) != "TTM": + if new_item.get("period") != "TTM": new_item.pop("dividend_yiel_percentage", None) if len(query.symbol.split(",")) == 1: diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py index 99e80b44376d..322c3946957c 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py @@ -210,7 +210,7 @@ async def aextract_data( async def callback(response: ClientResponse, session: ClientSession) -> list: """Return the response.""" - init_response = await response.json() + init_response: dict = await response.json() all_data: list = init_response.get(data_key, []) From f107b979f51e4e7b91ba3b90aeee52994662eeb2 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 17:12:48 -0400 Subject: [PATCH 04/17] type out of here --- .../fmp/openbb_fmp/models/financial_ratios.py | 2 +- .../providers/fmp/openbb_fmp/models/key_metrics.py | 2 +- .../openbb_intrinio/models/equity_historical.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index 4b4a7498e48f..ae275bacaa0e 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -242,7 +242,7 @@ async def aextract_data( async def response_callback( response: ClientResponse, session: ClientSession ) -> List[Dict]: - results: List[dict] = await response.json() + results: List[dict] = await response.json() # type: ignore symbol = response.url.parts[-1] # TTM data diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py b/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py index f88edcebffcf..a22723ff5fbb 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/key_metrics.py @@ -229,7 +229,7 @@ async def aextract_data( symbols = query.symbol.split(",") - results = [] + results = [] # type: ignore async def get_one(symbol): """Get data for one symbol.""" diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py index 322c3946957c..9d67ebda34d1 100644 --- a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py +++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_historical.py @@ -72,9 +72,9 @@ def set_time_params(cls, values: "IntrinioEquityHistoricalQueryParams"): } if values.interval in ["1m", "5m", "10m", "15m", "30m", "60m", "1h"]: - values._interval_size = values.interval + values._interval_size = values.interval # type: ignore elif values.interval in ["1d", "1W", "1M", "1Q", "1Y"]: - values._frequency = frequency_dict[values.interval] + values._frequency = frequency_dict[values.interval] # type: ignore return values @@ -210,7 +210,7 @@ async def aextract_data( async def callback(response: ClientResponse, session: ClientSession) -> list: """Return the response.""" - init_response: dict = await response.json() + init_response: dict = await response.json() # type: ignore all_data: list = init_response.get(data_key, []) @@ -219,8 +219,8 @@ async def callback(response: ClientResponse, session: ClientSession) -> list: url = response.url.update_query(next_page=next_page).human_repr() response_data = await session.get_json(url) - all_data.extend(response_data.get(data_key, [])) - next_page = response_data.get("next_page", None) + all_data.extend(response_data.get(data_key, [])) # type: ignore + next_page = response_data.get("next_page", None) # type: ignore return all_data From d5c80711e2139764a39136b1eab7dc5931d06cc8 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 17:35:39 -0400 Subject: [PATCH 05/17] Update financial_ratios.py --- .../fmp/openbb_fmp/models/financial_ratios.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index ae275bacaa0e..f66f632d2620 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -239,6 +239,8 @@ async def aextract_data( ttm_dict = {"period": "TTM", "date": datetime.now().strftime("%Y-%m-%d")} + include_ttm = query.period != "ttm" and query.with_ttm + async def response_callback( response: ClientResponse, session: ClientSession ) -> List[Dict]: @@ -247,15 +249,15 @@ async def response_callback( # TTM data ttm_url = f"{base_url}/ratios-ttm/{symbol}?&apikey={api_key}" - - if query.period == "ttm": - results = [{**ttm_dict, **item} for item in results] - elif query.with_ttm and (ratios_ttm := await session.get_one(ttm_url)): + if include_ttm and (ratios_ttm := await session.get_one(ttm_url)): results.insert( 0, {"symbol": symbol, **ttm_dict, **ratios_ttm}, ) + if query.period == "ttm": + results = [{**ttm_dict, **item} for item in results] + return results endpoint = "ratios" if query.period != "ttm" else "ratios-ttm" From 5fe65021799cf24983d8d6b9667a4c01e785a142 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 17:37:24 -0400 Subject: [PATCH 06/17] Update financial_ratios.py --- .../providers/fmp/openbb_fmp/models/financial_ratios.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index f66f632d2620..4a19c0932d58 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -262,9 +262,7 @@ async def response_callback( endpoint = "ratios" if query.period != "ttm" else "ratios-ttm" - urls = [ - (f"{base_url}/{endpoint}/{symbol}") for symbol in query.symbol.split(",") - ] + urls = [f"{base_url}/{endpoint}/{symbol}" for symbol in query.symbol.split(",")] kwargs.update( params={"period": query.period, "limit": query.limit, "apikey": api_key} From a884b2e8c6f800c0b57756df37aea103baf5f5f3 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 17:42:39 -0400 Subject: [PATCH 07/17] Update financial_ratios.py --- .../providers/fmp/openbb_fmp/models/financial_ratios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index 4a19c0932d58..3f9aee3b0013 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -256,7 +256,7 @@ async def response_callback( ) if query.period == "ttm": - results = [{**ttm_dict, **item} for item in results] + results = [{"symbol": symbol, **ttm_dict, **item} for item in results] return results From 85ab51bf88bf132fe91b0a4580715c1f74846c11 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 18:35:09 -0400 Subject: [PATCH 08/17] Update test_fmp_financial_ratios_fetcher.yaml --- .../test_fmp_financial_ratios_fetcher.yaml | 309 +++++++++--------- 1 file changed, 155 insertions(+), 154 deletions(-) diff --git a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml index 37d760efec65..d7635cff5a86 100644 --- a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml +++ b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml @@ -3,9 +3,9 @@ interactions: body: null headers: Accept: - - '*/*' + - application/json Accept-Encoding: - - gzip, deflate, br + - gzip, deflate Connection: - keep-alive method: GET @@ -13,160 +13,161 @@ interactions: response: body: string: !!binary | - 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 - 2ZRqdyx6AAA= + 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 headers: Access-Control-Allow-Credentials: - 'true' Access-Control-Allow-Headers: - X-Requested-With, content-type, auth-token, Authorization, stripe-signature, - APPS + APPS, publicauthkey, privateauthkey Access-Control-Allow-Methods: - GET, POST, OPTIONS Access-Control-Allow-Origin: @@ -180,9 +181,9 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Mon, 16 Oct 2023 12:22:44 GMT - ETag: - - W/"7a2c-IYeqKv0jiJULoj7Ss5Syn28DqQs" + - Fri, 19 Apr 2024 22:33:51 GMT + Etag: + - W/"7acf-oUoWJK4OrykSmGGjk6Uz+v3CV+o" Server: - nginx/1.18.0 (Ubuntu) Transfer-Encoding: From 79f1dba828abc3bdfc3dd2b2418f8ec8fb0e3885 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 18:55:56 -0400 Subject: [PATCH 09/17] test ? --- .../tests/app/logs/test_handlers_manager.py | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/openbb_platform/core/tests/app/logs/test_handlers_manager.py b/openbb_platform/core/tests/app/logs/test_handlers_manager.py index d46451c63146..46dc55e2efff 100644 --- a/openbb_platform/core/tests/app/logs/test_handlers_manager.py +++ b/openbb_platform/core/tests/app/logs/test_handlers_manager.py @@ -37,18 +37,26 @@ def __init__(self, settings): """Initialize the formatter.""" self.settings = settings + def _style(self): + """Return the style.""" + return logging.PercentStyle + def test_handlers_added_correctly(): """Test if the handlers are added correctly.""" - with patch( - "openbb_core.app.logs.handlers_manager.PosthogHandler", - MockPosthogHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", - MockPathTrackingFileHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", - MockFormatterWithExceptions, + with ( + patch( + "openbb_core.app.logs.handlers_manager.PosthogHandler", + MockPosthogHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", + MockPathTrackingFileHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", + MockFormatterWithExceptions, + ), ): settings = Mock() settings.handler_list = ["stdout", "stderr", "noop", "file", "posthog"] @@ -75,15 +83,19 @@ def test_handlers_added_correctly(): def test_update_handlers(): """Test if the handlers are updated correctly.""" - with patch( - "openbb_core.app.logs.handlers_manager.PosthogHandler", - MockPosthogHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", - MockPathTrackingFileHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", - MockFormatterWithExceptions, + with ( + patch( + "openbb_core.app.logs.handlers_manager.PosthogHandler", + MockPosthogHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", + MockPathTrackingFileHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", + MockFormatterWithExceptions, + ), ): settings = Mock() settings.handler_list = ["file", "posthog"] From 85f299e56ac21432928a11ba84518c6a463f8384 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 18:56:36 -0400 Subject: [PATCH 10/17] Revert "test ?" This reverts commit 79f1dba828abc3bdfc3dd2b2418f8ec8fb0e3885. --- .../tests/app/logs/test_handlers_manager.py | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/openbb_platform/core/tests/app/logs/test_handlers_manager.py b/openbb_platform/core/tests/app/logs/test_handlers_manager.py index 46dc55e2efff..d46451c63146 100644 --- a/openbb_platform/core/tests/app/logs/test_handlers_manager.py +++ b/openbb_platform/core/tests/app/logs/test_handlers_manager.py @@ -37,26 +37,18 @@ def __init__(self, settings): """Initialize the formatter.""" self.settings = settings - def _style(self): - """Return the style.""" - return logging.PercentStyle - def test_handlers_added_correctly(): """Test if the handlers are added correctly.""" - with ( - patch( - "openbb_core.app.logs.handlers_manager.PosthogHandler", - MockPosthogHandler, - ), - patch( - "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", - MockPathTrackingFileHandler, - ), - patch( - "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", - MockFormatterWithExceptions, - ), + with patch( + "openbb_core.app.logs.handlers_manager.PosthogHandler", + MockPosthogHandler, + ), patch( + "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", + MockPathTrackingFileHandler, + ), patch( + "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", + MockFormatterWithExceptions, ): settings = Mock() settings.handler_list = ["stdout", "stderr", "noop", "file", "posthog"] @@ -83,19 +75,15 @@ def test_handlers_added_correctly(): def test_update_handlers(): """Test if the handlers are updated correctly.""" - with ( - patch( - "openbb_core.app.logs.handlers_manager.PosthogHandler", - MockPosthogHandler, - ), - patch( - "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", - MockPathTrackingFileHandler, - ), - patch( - "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", - MockFormatterWithExceptions, - ), + with patch( + "openbb_core.app.logs.handlers_manager.PosthogHandler", + MockPosthogHandler, + ), patch( + "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", + MockPathTrackingFileHandler, + ), patch( + "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", + MockFormatterWithExceptions, ): settings = Mock() settings.handler_list = ["file", "posthog"] From a8124e5f38e1f493713e149ef13464bb10fd6b4d Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 18:57:04 -0400 Subject: [PATCH 11/17] Update test_handlers_manager.py --- .../tests/app/logs/test_handlers_manager.py | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/openbb_platform/core/tests/app/logs/test_handlers_manager.py b/openbb_platform/core/tests/app/logs/test_handlers_manager.py index d46451c63146..9b859941b1a5 100644 --- a/openbb_platform/core/tests/app/logs/test_handlers_manager.py +++ b/openbb_platform/core/tests/app/logs/test_handlers_manager.py @@ -36,19 +36,24 @@ class MockFormatterWithExceptions(logging.Formatter): def __init__(self, settings): """Initialize the formatter.""" self.settings = settings + self._style = logging.PercentStyle def test_handlers_added_correctly(): """Test if the handlers are added correctly.""" - with patch( - "openbb_core.app.logs.handlers_manager.PosthogHandler", - MockPosthogHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", - MockPathTrackingFileHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", - MockFormatterWithExceptions, + with ( + patch( + "openbb_core.app.logs.handlers_manager.PosthogHandler", + MockPosthogHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", + MockPathTrackingFileHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", + MockFormatterWithExceptions, + ), ): settings = Mock() settings.handler_list = ["stdout", "stderr", "noop", "file", "posthog"] @@ -75,15 +80,19 @@ def test_handlers_added_correctly(): def test_update_handlers(): """Test if the handlers are updated correctly.""" - with patch( - "openbb_core.app.logs.handlers_manager.PosthogHandler", - MockPosthogHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", - MockPathTrackingFileHandler, - ), patch( - "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", - MockFormatterWithExceptions, + with ( + patch( + "openbb_core.app.logs.handlers_manager.PosthogHandler", + MockPosthogHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.PathTrackingFileHandler", + MockPathTrackingFileHandler, + ), + patch( + "openbb_core.app.logs.handlers_manager.FormatterWithExceptions", + MockFormatterWithExceptions, + ), ): settings = Mock() settings.handler_list = ["file", "posthog"] From beddb60cdb4fec5c6d874414897ca3003bce2658 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 19:05:21 -0400 Subject: [PATCH 12/17] Update test_handlers_manager.py --- openbb_platform/core/tests/app/logs/test_handlers_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openbb_platform/core/tests/app/logs/test_handlers_manager.py b/openbb_platform/core/tests/app/logs/test_handlers_manager.py index 9b859941b1a5..1d0f865565ff 100644 --- a/openbb_platform/core/tests/app/logs/test_handlers_manager.py +++ b/openbb_platform/core/tests/app/logs/test_handlers_manager.py @@ -36,7 +36,6 @@ class MockFormatterWithExceptions(logging.Formatter): def __init__(self, settings): """Initialize the formatter.""" self.settings = settings - self._style = logging.PercentStyle def test_handlers_added_correctly(): From 76d8a17b0885d7feec4c4953eaef6bedf9e4c16a Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Fri, 19 Apr 2024 19:28:33 -0400 Subject: [PATCH 13/17] Update financial_ratios.py --- .../providers/fmp/openbb_fmp/models/financial_ratios.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index 3f9aee3b0013..587695a600e7 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -268,7 +268,7 @@ async def response_callback( params={"period": query.period, "limit": query.limit, "apikey": api_key} ) - return await amake_requests(urls, response_callback=response_callback, **kwargs) + return await amake_requests(urls, response_callback, **kwargs) @staticmethod def transform_data( @@ -277,7 +277,10 @@ def transform_data( """Return the transformed data.""" results: List[FMPFinancialRatiosData] = [] for item in data: - new_item = {to_snake_case(k).replace("ttm", ""): v for k, v in item.items()} + new_item = { + to_snake_case(k).replace("_ttm", "").replace("ttm", ""): v + for k, v in item.items() + } if new_item.get("period") != "TTM": new_item.pop("dividend_yiel_percentage", None) From 94da68186a1579dc7597f0e64364144790f2f489 Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Sat, 20 Apr 2024 13:14:39 -0400 Subject: [PATCH 14/17] remove `parse_date` --- .../openbb_fmp/models/equity_historical.py | 9 ++----- .../fmp/openbb_fmp/models/index_historical.py | 10 ++----- .../fmp/openbb_fmp/models/market_indices.py | 9 ++----- .../providers/fmp/openbb_fmp/utils/helpers.py | 27 ------------------- .../test_fmp_financial_ratios_fetcher.yaml | 2 +- 5 files changed, 7 insertions(+), 50 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py b/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py index 19ead52f7729..27e78bcf1019 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/equity_historical.py @@ -22,8 +22,8 @@ amake_request, get_querystring, ) -from openbb_fmp.utils.helpers import get_interval, parse_date -from pydantic import Field, field_validator +from openbb_fmp.utils.helpers import get_interval +from pydantic import Field class FMPEquityHistoricalQueryParams(EquityHistoricalQueryParams): @@ -61,11 +61,6 @@ class FMPEquityHistoricalData(EquityHistoricalData): json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) - @field_validator("date", mode="before", check_fields=False) - def date_validate(cls, v): # pylint: disable=E0213 - """Return formatted datetime.""" - return parse_date(v, "America/New_York") - class FMPEquityHistoricalFetcher( Fetcher[ diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py b/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py index 879d0b500dc1..33eabd6f5b3f 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/index_historical.py @@ -22,8 +22,8 @@ amake_request, get_querystring, ) -from openbb_fmp.utils.helpers import get_interval, parse_date -from pydantic import Field, field_validator +from openbb_fmp.utils.helpers import get_interval +from pydantic import Field class FMPIndexHistoricalQueryParams(IndexHistoricalQueryParams): @@ -57,12 +57,6 @@ class FMPIndexHistoricalData(IndexHistoricalData): json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) - @field_validator("date", mode="before", check_fields=False) - @classmethod - def date_validate(cls, v): # pylint: disable=E0213 - """Return formatted datetime.""" - return parse_date(v, "America/New_York") - class FMPIndexHistoricalFetcher( Fetcher[ diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py b/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py index e90e7777c921..15e4bb6719c5 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/market_indices.py @@ -13,8 +13,8 @@ ) from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS from openbb_core.provider.utils.helpers import get_querystring -from openbb_fmp.utils.helpers import get_data_many, parse_date -from pydantic import Field, NonNegativeInt, field_validator +from openbb_fmp.utils.helpers import get_data_many +from pydantic import Field, NonNegativeInt class FMPMarketIndicesQueryParams(MarketIndicesQueryParams): @@ -63,11 +63,6 @@ class FMPMarketIndicesData(MarketIndicesData): default=None, ) - @field_validator("date", mode="before", check_fields=False) - def date_validate(cls, v): # pylint: disable=E0213 - """Return formatted datetime.""" - return parse_date(v, "America/New_York") - class FMPMarketIndicesFetcher( Fetcher[ diff --git a/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py b/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py index 5634579eef8c..efd77138baac 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py +++ b/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py @@ -2,11 +2,9 @@ from datetime import ( date as dateType, - datetime, ) from typing import Any, Dict, List, Optional, Union -from dateutil import parser from openbb_core.provider.utils.client import ClientSession from openbb_core.provider.utils.errors import EmptyDataError from openbb_core.provider.utils.helpers import ( @@ -16,7 +14,6 @@ get_querystring, ) from pydantic import BaseModel -from pytz import timezone async def response_callback( @@ -149,27 +146,3 @@ def get_interval(value: str) -> str: } return f"{value[:-1]}{intervals[value[-1]]}" - - -# some fmp endpoint return date in EST without a timezone, this function will parse it -# and return a datetime object with the correct timezone -def parse_date(date: str, with_tz: Optional[str] = None) -> datetime: - """Parse the date string. - - Parameters - ---------- - date: str - The date string to parse. - with_tz: Optional[str] - Timezone the date is in. - - Returns - ------- - datetime - The parsed datetime. - """ - - date_dt = parser.isoparse(str(date)) - local = timezone(with_tz or "America/New_York") - - return local.localize(date_dt.replace(tzinfo=None)) if with_tz else date_dt diff --git a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml index d7635cff5a86..72caae94b6dc 100644 --- a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml +++ b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_financial_ratios_fetcher.yaml @@ -181,7 +181,7 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Fri, 19 Apr 2024 22:33:51 GMT + - Fri, 19 Apr 2024 23:29:29 GMT Etag: - W/"7acf-oUoWJK4OrykSmGGjk6Uz+v3CV+o" Server: From 87ec9c97bfb1186fa536899b4e123334b4d3708e Mon Sep 17 00:00:00 2001 From: tehcoderer Date: Sat, 20 Apr 2024 13:15:47 -0400 Subject: [PATCH 15/17] Update helpers.py --- openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py b/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py index efd77138baac..c37c5718cdc6 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py +++ b/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py @@ -1,8 +1,6 @@ """FMP Helpers Module.""" -from datetime import ( - date as dateType, -) +from datetime import date as dateType from typing import Any, Dict, List, Optional, Union from openbb_core.provider.utils.client import ClientSession From 4214037bffc68ef178f96873db2f43a0f2e37bb1 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:13:54 -0700 Subject: [PATCH 16/17] pop redundant fields --- .../providers/fmp/openbb_fmp/models/financial_ratios.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index 587695a600e7..d033c37381c0 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -281,10 +281,9 @@ def transform_data( to_snake_case(k).replace("_ttm", "").replace("ttm", ""): v for k, v in item.items() } - - if new_item.get("period") != "TTM": - new_item.pop("dividend_yiel_percentage", None) - + for col in ["dividend_yield", "pe_ratio", "peg_ratio"]: + if col in new_item: + _ = new_item.pop(col) if len(query.symbol.split(",")) == 1: new_item.pop("symbol", None) From 474274f3a36e03dbb608af30a968b3a39a8fea79 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:49:13 -0700 Subject: [PATCH 17/17] field mappings --- .../fmp/openbb_fmp/models/financial_ratios.py | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py index d033c37381c0..74c678fd300b 100644 --- a/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py +++ b/openbb_platform/providers/fmp/openbb_fmp/models/financial_ratios.py @@ -41,11 +41,14 @@ class FMPFinancialRatiosData(FinancialRatiosData): """FMP Financial Ratios Data.""" __alias_dict__ = { - "dividend_yield": "dividend_yiel", - "dividend_yield_percentage": "dividend_yiel_percentage", "period_ending": "date", "fiscal_period": "period", "fiscal_year": "calendar_year", + "dividend_yield": "dividend_yiel", + "cash_flow_coverage_ratio": "cash_flow_coverage_ratios", + "short_term_coverage_ratio": "short_term_coverage_ratios", + "cash_flow_to_debt": "cash_flow_to_debt_ratio", + "interest_coverage_ratio": "interest_coverage", } symbol: Optional[str] = Field( @@ -70,28 +73,44 @@ class FMPFinancialRatiosData(FinancialRatiosData): default=None, description="Cash conversion cycle." ) gross_profit_margin: Optional[float] = Field( - default=None, description="Gross profit margin." + default=None, + description="Gross profit margin.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) operating_profit_margin: Optional[float] = Field( - default=None, description="Operating profit margin." + default=None, + description="Operating profit margin.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) pretax_profit_margin: Optional[float] = Field( - default=None, description="Pretax profit margin." + default=None, + description="Pretax profit margin.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) net_profit_margin: Optional[float] = Field( - default=None, description="Net profit margin." + default=None, + description="Net profit margin.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) effective_tax_rate: Optional[float] = Field( - default=None, description="Effective tax rate." + default=None, + description="Effective tax rate.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) return_on_assets: Optional[float] = Field( - default=None, description="Return on assets." + default=None, + description="Return on assets.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) return_on_equity: Optional[float] = Field( - default=None, description="Return on equity." + default=None, + description="Return on equity.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) return_on_capital_employed: Optional[float] = Field( - default=None, description="Return on capital employed." + default=None, + description="Return on capital employed.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) net_income_per_ebt: Optional[float] = Field( default=None, description="Net income per EBT." @@ -110,10 +129,10 @@ class FMPFinancialRatiosData(FinancialRatiosData): total_debt_to_capitalization: Optional[float] = Field( default=None, description="Total debt to capitalization." ) - interest_coverage: Optional[float] = Field( + interest_coverage_ratio: Optional[float] = Field( default=None, description="Interest coverage." ) - cash_flow_to_debt_ratio: Optional[float] = Field( + cash_flow_to_debt: Optional[float] = Field( default=None, description="Cash flow to debt ratio." ) company_equity_multiplier: Optional[float] = Field( @@ -146,21 +165,15 @@ class FMPFinancialRatiosData(FinancialRatiosData): free_cash_flow_operating_cash_flow_ratio: Optional[float] = Field( default=None, description="Free cash flow operating cash flow ratio." ) - cash_flow_coverage_ratios: Optional[float] = Field( - default=None, description="Cash flow coverage ratios." + cash_flow_coverage_ratio: Optional[float] = Field( + default=None, description="Cash flow coverage ratio." ) - short_term_coverage_ratios: Optional[float] = Field( - default=None, description="Short term coverage ratios." + short_term_coverage_ratio: Optional[float] = Field( + default=None, description="Short term coverage ratio." ) capital_expenditure_coverage_ratio: Optional[float] = Field( default=None, description="Capital expenditure coverage ratio." ) - dividend_paid_and_capex_coverage_ratio: Optional[float] = Field( - default=None, description="Dividend paid and capex coverage ratio." - ) - dividend_payout_ratio: Optional[float] = Field( - default=None, description="Dividend payout ratio." - ) price_book_value_ratio: Optional[float] = Field( default=None, description="Price book value ratio." ) @@ -188,9 +201,16 @@ class FMPFinancialRatiosData(FinancialRatiosData): price_sales_ratio: Optional[float] = Field( default=None, description="Price sales ratio." ) - dividend_yield: Optional[float] = Field(default=None, description="Dividend yield.") - dividend_yield_percentage: Optional[float] = Field( - default=None, description="Dividend yield percentage." + dividend_paid_and_capex_coverage_ratio: Optional[float] = Field( + default=None, description="Dividend paid and capex coverage ratio." + ) + dividend_payout_ratio: Optional[float] = Field( + default=None, description="Dividend payout ratio." + ) + dividend_yield: Optional[float] = Field( + default=None, + description="Dividend yield.", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, ) dividend_per_share: Optional[float] = Field( default=None, description="Dividend per share." @@ -281,7 +301,7 @@ def transform_data( to_snake_case(k).replace("_ttm", "").replace("ttm", ""): v for k, v in item.items() } - for col in ["dividend_yield", "pe_ratio", "peg_ratio"]: + for col in ["dividend_yiel_percentage", "pe_ratio", "peg_ratio"]: if col in new_item: _ = new_item.pop(col) if len(query.symbol.split(",")) == 1: