diff --git a/src/rapidata/api_client/lazy_model.py b/src/rapidata/api_client/lazy_model.py index 89ff89a43..3a9e4987b 100644 --- a/src/rapidata/api_client/lazy_model.py +++ b/src/rapidata/api_client/lazy_model.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, TYPE_CHECKING from pydantic import BaseModel, ConfigDict, ValidationError @@ -122,38 +122,45 @@ def _lazy_construct( # ------------------------------------------------------------------ # Access-time guard – raise only when a bad field is actually read # ------------------------------------------------------------------ - def __getattribute__(self, name: str) -> Any: - # Only intercept known model fields; everything else takes the - # fast super() path (Pydantic internals, dunder attrs, methods). - # Use getattr (MRO-aware) rather than type(self).__dict__.get(): - # in Pydantic v2, fields are exposed via the `model_fields` - # class-property and are NOT present in the class's own __dict__, - # so a __dict__ lookup always misses and the guard never fires. - model_fields = getattr(type(self), "model_fields", None) - if model_fields is not None and name in model_fields: - # model_construct() bypasses __init__, so `_field_validation_errors` - # may not be set on every instance. Treat missing as "no errors". - errors: Optional[Dict[str, dict]] - try: - errors = object.__getattribute__( - self, "_field_validation_errors" - ) - except AttributeError: - errors = None - if errors and name in errors: - err = errors[name] - message = ( - f"Field '{name}' on {type(self).__name__} has an unexpected " - f"type from the backend: {err.get('msg', err)}" - ) - # Imported lazily to avoid pulling the higher SDK layer at - # module import time (this base class is imported by every - # generated model). - from rapidata.rapidata_client.api.rapidata_api_client import ( - format_outdated_sdk_note, - ) - note = format_outdated_sdk_note() - if note: - message = f"{message}\n{note}" - raise TypeError(message) - return super().__getattribute__(name) + # Guarded against TYPE_CHECKING for the same reason Pydantic guards its own + # __getattr__/__setattr__ (pydantic/main.py): a class-visible __getattribute__ + # returning Any makes type checkers treat *every* attribute access as a valid + # Any, which silently hides renamed/removed backend fields (e.g. .status after + # it became .state). Hiding it from pyright restores per-field access checking + # against the declared model annotations while leaving runtime behaviour intact. + if not TYPE_CHECKING: + + def __getattribute__(self, name: str) -> Any: + # Only intercept known model fields; everything else takes the + # fast super() path (Pydantic internals, dunder attrs, methods). + # Use getattr (MRO-aware) rather than type(self).__dict__.get(): + # in Pydantic v2, fields are exposed via the `model_fields` + # class-property and are NOT present in the class's own __dict__, + # so a __dict__ lookup always misses and the guard never fires. + model_fields = getattr(type(self), "model_fields", None) + if model_fields is not None and name in model_fields: + # model_construct() bypasses __init__, so `_field_validation_errors` + # may not be set on every instance. Treat missing as "no errors". + errors: Optional[Dict[str, dict]] + try: + errors = object.__getattribute__(self, "_field_validation_errors") + except AttributeError: + errors = None + if errors and name in errors: + err = errors[name] + message = ( + f"Field '{name}' on {type(self).__name__} has an unexpected " + f"type from the backend: {err.get('msg', err)}" + ) + # Imported lazily to avoid pulling the higher SDK layer at + # module import time (this base class is imported by every + # generated model). + from rapidata.rapidata_client.api.rapidata_api_client import ( + format_outdated_sdk_note, + ) + + note = format_outdated_sdk_note() + if note: + message = f"{message}\n{note}" + raise TypeError(message) + return super().__getattribute__(name) diff --git a/src/rapidata/rapidata_client/job/rapidata_job.py b/src/rapidata/rapidata_client/job/rapidata_job.py index 358fc4e33..83d1bf71a 100644 --- a/src/rapidata/rapidata_client/job/rapidata_job.py +++ b/src/rapidata/rapidata_client/job/rapidata_job.py @@ -160,7 +160,9 @@ def get_status(self) -> str: The current status of the job as a string. """ with tracer.start_as_current_span("RapidataJob.get_status"): - return self._openapi_service.order.job_api.job_job_id_get(self.id).status + return self._openapi_service.order.job_api.job_job_id_get( + self.id + ).state.value def get_results(self) -> RapidataResults: """