tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[WithdrawSettlementBalanceResponse] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetSettlementBalanceWithdrawalResponse] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[SettlementBalanceHistoryEntry] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[MarginSubtraderGroup] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetMarginSubtraderGroupsResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginSubtraderGroupRequest] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginSubtraderGroupResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[UpdateMarginSubtraderGroupRequest] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetSettlementPricesResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[EstimatePortfolioMaintenanceMarginPosition] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[EstimatePortfolioMaintenanceMarginRequest] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[EstimatePortfolioMaintenanceMarginResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[MarginFcmApiKey] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[ListMarginFcmApiKeysResponse] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginFcmApiKeyRequest] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginFcmApiKeyResponse] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GenerateMarginFcmApiKeyRequest] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GenerateMarginFcmApiKeyResponse] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[MemberFundingPayment] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetMemberFundingPaymentsResponse] PASSED [100%]
=================================== FAILURES ===================================
________________ test_every_spec_endpoint_is_mapped[perps_scm] _________________
spec_loader = <function _load_perps_scm_spec at 0x7fc468f69940>
endpoint_map = [MethodEndpointEntry(sdk_method='kalshi.perps.klear.resources.margin.MarginResource.margin_reports', http_method='GET'...p_method='GET', path_template='/margin/obligations/{obligation_id}/settlement_details', request_body_schema=None), ...]
label = 'perps_scm'
@pytest.mark.parametrize(
("spec_loader", "endpoint_map", "label"),
[
(_load_spec, METHOD_ENDPOINT_MAP, "core"),
(_load_perps_spec, PERPS_METHOD_ENDPOINT_MAP, "perps"),
(_load_perps_scm_spec, PERPS_SCM_METHOD_ENDPOINT_MAP, "perps_scm"),
],
ids=["core", "perps", "perps_scm"],
)
def test_every_spec_endpoint_is_mapped(
spec_loader: Any,
endpoint_map: list[MethodEndpointEntry],
label: str,
) -> None:
"""Fail if a spec operation is neither mapped nor explicitly unimplemented.
Closes the false-negative gap where a newly-ADDED upstream endpoint shipped
untested because nothing asserted spec paths ⊆ ``METHOD_ENDPOINT_MAP``. The
reverse direction (a map entry whose path the spec dropped) is caught by the
param-drift tests, which fail loud when the path is absent from the spec.
"""
spec_ops = _spec_operations(spec_loader())
mapped = {(e.http_method.upper(), e.path_template) for e in endpoint_map}
unmapped = spec_ops - mapped - set(_UNIMPLEMENTED_ENDPOINTS)
if unmapped:
> pytest.fail(
f"{label}: spec operations not in the endpoint map (add a "
f"MethodEndpointEntry, or record in _UNIMPLEMENTED_ENDPOINTS with a reason):\n"
+ "\n".join(f" - {m} {p}" for m, p in sorted(unmapped))
)
E Failed: perps_scm: spec operations not in the endpoint map (add a MethodEndpointEntry, or record in _UNIMPLEMENTED_ENDPOINTS with a reason):
E - GET /margin/estimate_maintenance_margin/metadata
E - GET /margin/funding_estimate_by_asset_class
tests/test_contracts.py:1299: Failed
_______ TestRequestBodyDrift.test_body_properties_match_spec[create_rfq] _______
self = <tests.test_contracts.TestRequestBodyDrift object at 0x7fc468e61f70>
entry = MethodEndpointEntry(sdk_method='kalshi.resources.communications.CommunicationsResource.create_rfq', http_method='POST', path_template='/communications/rfqs', request_body_schema='#/components/schemas/CreateRFQRequest')
def test_body_properties_match_spec(
self,
entry: MethodEndpointEntry,
) -> None:
assert entry.request_body_schema is not None
model_fqn = BODY_MODEL_MAP.get(entry.request_body_schema)
assert model_fqn is not None, (
f"No request model registered in BODY_MODEL_MAP for "
f"{entry.request_body_schema!r}. Add the mapping."
)
model_cls = _get_model_class_from_fqn(model_fqn)
schema = _resolve_request_body_schema(
self.spec,
entry.path_template,
entry.http_method,
)
assert schema is not None, (
f"Spec has no body schema for {entry.http_method} {entry.path_template}"
)
errors: list[str] = []
_check_model_drift(
model_cls=model_cls,
model_fqn=model_fqn,
spec_schema=schema,
errors=errors,
)
# Issue #52: also drift-check nested BaseModel fields (one level deep).
for field_name, nested_cls, nested_schema in _iter_nested_body_models(
model_cls,
schema,
self.spec,
):
nested_fqn = f"{nested_cls.__module__}.{nested_cls.__qualname__}"
_check_model_drift(
model_cls=nested_cls,
model_fqn=nested_fqn,
spec_schema=nested_schema,
errors=errors,
context=f"{model_fqn}.{field_name}",
)
if errors:
> pytest.fail(f"{model_fqn} <-> {entry.request_body_schema}\n" + "\n".join(errors))
E Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
E [ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']
tests/test_contracts.py:1724: Failed
________ TestRequestBodyDrift.test_body_properties_match_spec[create2] _________
self = <tests.test_contracts.TestRequestBodyDrift object at 0x7fc468e621b0>
entry = MethodEndpointEntry(sdk_method='kalshi.resources.communications.RFQsResource.create', http_method='POST', path_template='/communications/rfqs', request_body_schema='#/components/schemas/CreateRFQRequest')
def test_body_properties_match_spec(
self,
entry: MethodEndpointEntry,
) -> None:
assert entry.request_body_schema is not None
model_fqn = BODY_MODEL_MAP.get(entry.request_body_schema)
assert model_fqn is not None, (
f"No request model registered in BODY_MODEL_MAP for "
f"{entry.request_body_schema!r}. Add the mapping."
)
model_cls = _get_model_class_from_fqn(model_fqn)
schema = _resolve_request_body_schema(
self.spec,
entry.path_template,
entry.http_method,
)
assert schema is not None, (
f"Spec has no body schema for {entry.http_method} {entry.path_template}"
)
errors: list[str] = []
_check_model_drift(
model_cls=model_cls,
model_fqn=model_fqn,
spec_schema=schema,
errors=errors,
)
# Issue #52: also drift-check nested BaseModel fields (one level deep).
for field_name, nested_cls, nested_schema in _iter_nested_body_models(
model_cls,
schema,
self.spec,
):
nested_fqn = f"{nested_cls.__module__}.{nested_cls.__qualname__}"
_check_model_drift(
model_cls=nested_cls,
model_fqn=nested_fqn,
spec_schema=nested_schema,
errors=errors,
context=f"{model_fqn}.{field_name}",
)
if errors:
> pytest.fail(f"{model_fqn} <-> {entry.request_body_schema}\n" + "\n".join(errors))
E Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
E [ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']
tests/test_contracts.py:1724: Failed
=============================== warnings summary ===============================
tests/test_contracts.py::TestSpecDrift::test_additive_drift[RFQ]
/home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:837: AdditiveOptionalDriftWarning: Additive-optional drift in kalshi.models.communications.RFQ: Spec field 'target_cost_excludes_fees' has no SDK mapping
_warn_additive_optional(entry.sdk_model, additive_optional)
tests/test_contracts.py::TestSpecDrift::test_additive_drift[Quote]
/home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:837: AdditiveOptionalDriftWarning: Additive-optional drift in kalshi.models.communications.Quote: Spec field 'target_cost_excludes_fees' has no SDK mapping
_warn_additive_optional(entry.sdk_model, additive_optional)
tests/test_contracts.py::TestSpecDrift::test_additive_drift[CreateRFQRequest]
/home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:837: AdditiveOptionalDriftWarning: Additive-optional drift in kalshi.models.communications.CreateRFQRequest: Spec field 'target_cost_excludes_fees' has no SDK mapping
_warn_additive_optional(entry.sdk_model, additive_optional)
tests/test_contracts.py::TestAdditiveDriftClassification::test_optional_drift_survives_the_nightly_strict_gate
/home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:988: AdditiveOptionalDriftWarning: Additive-optional drift in tests.synthetic.Fake: Spec field 'opt_new' has no SDK mapping
_warn_additive_optional("tests.synthetic.Fake", msgs)
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_contracts.py::test_every_spec_endpoint_is_mapped[perps_scm] - Failed: perps_scm: spec operations not in the endpoint map (add a MethodEndpointEntry, or record in _UNIMPLEMENTED_ENDPOINTS with a reason):
- GET /margin/estimate_maintenance_margin/metadata
- GET /margin/funding_estimate_by_asset_class
FAILED tests/test_contracts.py::TestRequestBodyDrift::test_body_properties_match_spec[create_rfq] - Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
[ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']
FAILED tests/test_contracts.py::TestRequestBodyDrift::test_body_properties_match_spec[create2] - Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
[ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']
============ 3 failed, 778 passed, 4 warnings in 300.03s (0:05:00) =============
Nightly strict contract tests failed
The scheduled
Spec Drift Detectionrun found the SDK contract suite no longer matches upstream OpenAPI/AsyncAPI.To resolve: locally run
uv run python scripts/sync_spec.py+uv run python scripts/generate.py, reconcile models/maps, and open a PR withCloses #<this issue>. This tracker auto-closes on the next green scheduled run.To silence while a reconcile is in flight, add the
spec-drift-acklabel — nightly runs then stop commenting until the failing set changes.Failing test output (last 200 lines)