Skip to content

0.19.2

Latest

Choose a tag to compare

@igorbenav igorbenav released this 15 Nov 19:50
· 8 commits to main since this release
775128c

FastCRUD 0.19.2

Warning

Deprecated Behavior - Action Required for Next Major Version

The create() method will change its behavior in v0.20.0 for consistency with other CRUD methods. Current usage patterns will show deprecation warnings:

# ⚠️ Will return None instead of SQLAlchemy model in v0.20.0
user = await user_crud.create(db=db, object=user_data)
# Warning: create() without schema_to_select will return None instead of the SQLAlchemy model

# ⚠️ Will properly respect schema_to_select plus return_as_model=False parameter in v0.20.0  
user = await user_crud.create(db=db, object=user_data, schema_to_select=UserRead)
# Warning: create() with schema_to_select and return_as_model=False will default to returning dict

# ✅ Future-compatible usage (works now and in v0.20.0)
user_dict = await user_crud.create(
    db=db, 
    object=user_data, 
    schema_to_select=UserRead,
    return_as_model=False  # Explicit: returns dict
)

user_model = await user_crud.create(
    db=db, 
    object=user_data, 
    schema_to_select=UserRead, 
    return_as_model=True   # Explicit: returns Pydantic model
)

Added

  • get_joined Method Overloads by @igorbenav
    • Added missing @overload signatures for get_joined() method to support proper type inference
    • Added return_as_model parameter for converting joined results to Pydantic models
    • Enhanced type safety: returns SelectSchemaType when return_as_model=True, dict when False
    • Consistent API with other CRUD methods like get(), update(), etc.

Improved

  • create Method Performance and Consistency by @igorbenav
    • Removed unnecessary database round-trip by eliminating redundant get() call after creation
    • Added deprecation warnings for upcoming API consistency changes in next major version
    • Fixed type hints to match actual implementation behavior (removed incorrect None return type)

Deprecated

  • create Method Behavior Changes (Warnings Added)
    • create() without schema_to_select will return None instead of SQLAlchemy model in next major version
    • create() with schema_to_select will properly respect return_as_model parameter in next major version
    • These changes align create() behavior with update() and other CRUD methods for consistency

Fixed

  • Type Safety Issues by @igorbenav
    • Fixed get_joined() method type annotations to properly reflect actual return types
    • Corrected create() method type hints to remove impossible None return type
    • Enhanced test coverage with 8 new comprehensive tests for get_joined return type variations

Breaking Changes

⚠️ None - This release maintains full backward compatibility with 0.19.1. Deprecation warnings provide clear migration path for next major version.

Details


get_joined Method Overloads and return_as_model Parameter

Problem Solved

The get_joined() method was missing proper type overloads and the return_as_model parameter, creating inconsistency with other CRUD methods:

# Before: No return_as_model parameter, unclear return types
result = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_prefix="tier_",
    schema_to_select=UserWithTier
)
# Type: dict[str, Any] - always dict, no model option

Solution Implemented

Added complete overload signatures and return_as_model parameter to match other CRUD methods:

# After: Clear return types based on parameters
user_dict = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_prefix="tier_",
    schema_to_select=UserWithTier,
    return_as_model=False  # Default: dict
)
# Type: Optional[dict[str, Any]]

user_model = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_prefix="tier_",
    schema_to_select=UserWithTier,
    return_as_model=True  # Pydantic model
)
# Type: Optional[UserWithTier]

New Overload Pattern

@overload
async def get_joined(
    self,
    db: AsyncSession,
    *,
    schema_to_select: type[SelectSchemaType],
    return_as_model: Literal[True],
    **kwargs: Any,
) -> Optional[SelectSchemaType]: ...

@overload
async def get_joined(
    self,
    db: AsyncSession,
    *,
    schema_to_select: type[SelectSchemaType],
    return_as_model: Literal[False] = False,
    **kwargs: Any,
) -> Optional[dict[str, Any]]: ...

Benefits

  • API Consistency: get_joined() now matches get(), update(), and other methods
  • Type Safety: Precise return types based on return_as_model parameter
  • Flexibility: Choose between dict access or Pydantic model validation
  • Enhanced IDE Support: Better autocomplete and type checking

create Method Performance and Consistency Improvements

Problem Solved

The create() method had several inconsistencies compared to other CRUD methods:

  1. Performance Issue: Unnecessary database round-trip when using schema_to_select
  2. Inconsistent Behavior: Didn't behave like update() and other methods
  3. Type Confusion: Return types didn't match actual implementation
# Before: Inconsistent behavior
user = await user_crud.create(db=db, object=user_data)
# Returns: SQLAlchemy model (different from update())

user = await user_crud.create(
    db=db, 
    object=user_data, 
    schema_to_select=UserRead
)
# Triggers unnecessary get() call after creation

Solution Implemented

  1. Eliminated Redundant Database Query: Removed unnecessary get() call after creation
  2. Added Deprecation Warnings: Clear migration path for next major version
  3. Fixed Type Hints: Accurate types matching current implementation
# Current behavior with deprecation warnings
user = await user_crud.create(db=db, object=user_data)
# Warning: Will return None instead of SQLAlchemy model in next major version

user = await user_crud.create(
    db=db,
    object=user_data,
    schema_to_select=UserRead,
    return_as_model=True
)
# No extra DB query, direct conversion to Pydantic model

Performance Impact

Before: create() with schema_to_select = 2 database operations

  1. INSERT statement to create record
  2. SELECT statement via get() call to fetch created data

After: create() with schema_to_select = 1 database operation

  1. INSERT statement with direct result conversion

Deprecation Migration Guide

Current v0.19.2 (with warnings):

# Case 1: No schema_to_select (shows warning)
user = await crud.create(db=db, object=user_data)
# Returns: SQLAlchemy model
# Warning: Will return None in next major version

# Case 2: With schema_to_select (shows warning if no explicit return_as_model)
user = await crud.create(db=db, object=user_data, schema_to_select=UserRead)
# Returns: dict (default return_as_model=False)
# Warning: Behavior will be consistent in next major version

Future v0.20.0:

# Case 1: No schema_to_select
user = await crud.create(db=db, object=user_data)
# Returns: None (like update() method)

# Case 2: With schema_to_select
user_dict = await crud.create(
    db=db, 
    object=user_data, 
    schema_to_select=UserRead,
    return_as_model=False
)
# Returns: dict

user_model = await crud.create(
    db=db, 
    object=user_data, 
    schema_to_select=UserRead,
    return_as_model=True
)
# Returns: Pydantic model

Benefits

  • Performance: Up to 50% faster creation with schema_to_select
  • Consistency: Matches behavior of update() and other CRUD methods
  • Type Safety: Clear, predictable return types
  • Migration Path: Deprecation warnings guide users to new patterns

Enhanced Test Coverage

Added 8 comprehensive tests covering all get_joined return type scenarios:

  • SQLAlchemy tests: 4 new test cases
  • SQLModel tests: 4 new test cases
  • Coverage: All combinations of return_as_model parameter usage
  • Validation: Proper type checking and error handling

What's Changed

Full Changelog: v0.19.1...v0.19.2