Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/fmp-py/python/fmp/_native/indexes/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class IndexConstituent:
@property
def cik(self) -> builtins.str: ...
@property
def founded(self) -> datetime.date: ...
def founded(self) -> builtins.str: ...
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
def __new__(
cls,
Expand All @@ -105,7 +105,7 @@ class IndexConstituent:
head_quarter: builtins.str,
date_first_added: typing.Optional[datetime.date],
cik: builtins.str,
founded: datetime.date,
founded: builtins.str,
) -> IndexConstituent: ...
def __getnewargs_ex__(self) -> tuple[tuple, dict]: ...
def __repr__(self) -> builtins.str: ...
Expand Down
6 changes: 3 additions & 3 deletions crates/fmp-py/src/models/indexes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub(crate) struct IndexConstituent {
#[pyo3(get)]
pub cik: String,
#[pyo3(get)]
pub founded: ::chrono::NaiveDate,
pub founded: String,
}

#[gen_stub_pymethods]
Expand All @@ -148,7 +148,7 @@ impl IndexConstituent {
head_quarter: String,
date_first_added: Option<::chrono::NaiveDate>,
cik: String,
founded: ::chrono::NaiveDate,
founded: String,
) -> Self {
Self {
symbol,
Expand Down Expand Up @@ -279,7 +279,7 @@ impl From<libfmp::responses::indexes::IndexConstituent> for IndexConstituent {
head_quarter: value.head_quarter,
date_first_added: value.date_first_added.map(|value| value.into_inner()),
cik: value.cik.into_inner(),
founded: value.founded.into_inner(),
founded: value.founded,
}
}
}
Expand Down
20 changes: 16 additions & 4 deletions crates/fmp-py/tests/test_indexes_b.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"/sp500-constituent",
"indexes_sp500_constituents.json",
"HONA",
datetime.date(1913, 12, 31),
"1902/1985",
datetime.date(2026, 6, 29),
id="sp500",
),
Expand All @@ -32,7 +32,7 @@
"/nasdaq-constituent",
"indexes_nasdaq_constituents.json",
"ADBE",
datetime.date(1982, 12, 1),
"1982-12-01",
None,
id="nasdaq",
),
Expand All @@ -41,7 +41,7 @@
"/dowjones-constituent",
"indexes_dow_jones_constituents.json",
"GOOGL",
datetime.date(1998, 9, 4),
"1998-09-04",
datetime.date(2026, 6, 29),
id="dow-jones",
),
Expand All @@ -54,7 +54,7 @@ def test_current_constituents_take_no_arguments(
path: str,
fixture: str,
symbol: str,
founded: datetime.date,
founded: str,
date_first_added: datetime.date | None,
) -> None:
"""Each current-constituent method hits its own path with no query and decodes the date fields."""
Expand All @@ -72,6 +72,18 @@ def test_current_constituents_take_no_arguments(
assert row.head_quarter


@pytest.mark.parametrize("founded", ["1994", "1902/1985", "1902/1985/2001"])
def test_constituent_founded_keeps_the_year_forms_verbatim(
client: Any, fixture_server: FixtureServer, founded: str
) -> None:
"""``founded`` is the provider's founding year or years, returned as the exact string."""
body = load_fixture("indexes_sp500_constituents.json")
body[0]["founded"] = founded
fixture_server.route("/sp500-constituent", body)

assert client.indexes.sp500_constituents()[0].founded == founded


@pytest.mark.parametrize(
("method", "path", "fixture", "symbol", "removed_ticker", "date"),
[
Expand Down
2 changes: 1 addition & 1 deletion crates/fmp-py/tests/typecheck_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def check_indexes_contract(client: FmpClient) -> None:
bar_time: datetime.datetime = intraday[0].date
members: list[IndexConstituent] = client.indexes.sp500_constituents()
first_added: datetime.date | None = members[0].date_first_added
founded: datetime.date = members[0].founded
founded: str = members[0].founded
_ = (currency, bar_date, vwap, bar_time, first_added, founded)


Expand Down
5 changes: 4 additions & 1 deletion crates/libfmp/src/responses/indexes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ pub struct IndexConstituent {
#[serde(deserialize_with = "required_option")]
pub date_first_added: Option<Date>,
pub cik: Cik,
pub founded: Date,
/// Founding year or years as the provider sends them, kept verbatim: a
/// year (`"1994"`), several years joined by `/` (`"1902/1985"`), or a full
/// date (`"1982-12-01"`) on some indexes. It is not a date.
pub founded: String,
}

/// One documented historical addition to and removal from a major US index.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
"cik": "0002089271",
"dateFirstAdded": "2026-06-29",
"founded": "1913-12-31",
"founded": "1902/1985",
"headQuarter": "Charlotte, North Carolina",
"name": "Honeywell Aerospace Inc",
"sector": "Technology",
Expand Down
14 changes: 14 additions & 0 deletions crates/libfmp/tests/indexes_constituent_responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ fn all_six_documented_constituent_fixtures_match_the_typed_wire_contract_exactly
current[0].date_first_added.unwrap().to_string(),
"2026-06-29"
);
assert_eq!(current[0].founded, "1902/1985");

let nullable: Vec<IndexConstituent> = serde_json::from_slice(NASDAQ).unwrap();
assert_eq!(nullable[0].cik.as_str(), "0000796343");
assert_eq!(nullable[0].founded, "1982-12-01");
assert_eq!(nullable[0].date_first_added, None);

let history: Vec<HistoricalIndexConstituent> =
Expand Down Expand Up @@ -98,6 +100,18 @@ fn current_constituent_fields_are_required_and_nullable_date_stays_required() {
assert_eq!(decoded.symbol.as_str(), "ADBE");
}

#[test]
fn founded_keeps_year_multi_year_and_date_forms_verbatim() {
let source: Value = serde_json::from_slice(NASDAQ).unwrap();
for founded in ["1994", "1902/1985", "1902/1985/2001", "1982-12-01", ""] {
let mut row = source[0].clone();
row["founded"] = json!(founded);
let decoded: IndexConstituent = serde_json::from_value(row.clone()).unwrap();
assert_eq!(decoded.founded, founded);
assert_eq!(serde_json::to_value(decoded).unwrap(), row);
}
}

#[test]
fn historical_fields_are_required_while_omittable_members_accept_null_or_empty() {
let source: Value = serde_json::from_slice(HISTORICAL_NASDAQ).unwrap();
Expand Down
4 changes: 2 additions & 2 deletions sdk/go/indexes_models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions sdk/go/indexes_parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func TestDocumentedIndexConstituentFixturesDecodeExactValues(t *testing.T) {
t.Parallel()
sp500 := assertFixtureParity[IndexConstituent](t, "indexes_sp500_constituents.json")
if len(sp500) != 1 || sp500[0].Symbol != "HONA" || sp500[0].CIK != "0002089271" ||
sp500[0].SubSector != "Aerospace & Defense" || sp500[0].Founded != mustParseDate(t, "1913-12-31") {
sp500[0].SubSector != "Aerospace & Defense" || sp500[0].Founded != "1902/1985" {
t.Fatalf("indexes_sp500_constituents = %+v", sp500)
}
if got := sp500[0].DateFirstAdded; got == nil || got.String() != "2026-06-29" {
Expand All @@ -117,7 +117,7 @@ func TestDocumentedIndexConstituentFixturesDecodeExactValues(t *testing.T) {

nasdaq := assertFixtureParity[IndexConstituent](t, "indexes_nasdaq_constituents.json")
if len(nasdaq) != 1 || nasdaq[0].Symbol != "ADBE" || nasdaq[0].CIK != "0000796343" ||
nasdaq[0].HeadQuarter != "San Jose, CA" {
nasdaq[0].HeadQuarter != "San Jose, CA" || nasdaq[0].Founded != "1982-12-01" {
t.Fatalf("indexes_nasdaq_constituents = %+v", nasdaq)
}
if nasdaq[0].DateFirstAdded != nil {
Expand Down Expand Up @@ -172,6 +172,12 @@ func TestIndexesRequiredMembersAreEnforcedLikeSerde(t *testing.T) {
if err := json.Unmarshal([]byte(wire), &rows); err != nil || len(rows) != 1 || rows[0].DateFirstAdded != nil {
t.Fatalf("null dateFirstAdded with unknown member: %+v, %v", rows, err)
}
for _, founded := range []string{"1994", "1902/1985", "1902/1985/2001"} {
wire = `[{"dateFirstAdded":null,` + strings.Replace(constituent, "1982-12-01", founded, 1) + `}]`
if err := json.Unmarshal([]byte(wire), &rows); err != nil || len(rows) != 1 || rows[0].Founded != founded {
t.Fatalf("founded %q: %+v, %v", founded, rows, err)
}
}
var changes []HistoricalIndexConstituent
wire = `[{"removedSecurity":null,"removedTicker":null,` + history + `,"futureField":[1,2,3]}]`
if err := json.Unmarshal([]byte(wire), &changes); err != nil || len(changes) != 1 ||
Expand Down
Loading