hamilton icon indicating copy to clipboard operation
hamilton copied to clipboard

Fix: issue #1297: datasaver does not work with __future__.annotations

Open kreczko opened this issue 9 months ago • 4 comments

Fixes issue #1297 by changing return_annotation retrieval function_modifiers.adapters.datasaver.validate.

Changes

  • modified datasaver.validate to check against __future__.annotations compatible type hints

How I tested this

  • added from __future__ import annotations to test_adapters
  • ran pytest tests/function_modifiers → test failed
  • made above changes
  • ran pytest tests/function_modifiers → test succeeded

Notes

`correct_ds_function` in `test_adapters.py` uses an alias for `dict`: `dict_ = dict`. However, for the string comparison this is not resolved to `dict`; added `dict_` to the validation for now.

I do not understand why this is done in the tests and should, in theory, not impact "normal" use. Feedback on this is welcome.

Checklist

  • [x] PR has an informative and human-readable title (this will be pulled into the release notes)
  • [x] Changes are limited to a single goal (no scope creep)
  • [x] Code passed the pre-commit check & code is left cleaner/nicer than when first encountered.
  • [x] Any change in functionality is tested
  • [x] New functions are documented (with a description, list of inputs, and expected output)
  • [x] Placeholder code is flagged / future TODOs are captured in comments
  • [x] Project documentation has been updated if adding/changing functionality.

kreczko avatar Mar 28 '25 16:03 kreczko

The bot makes a good suggestion: instead of my change, it is better to change # return_annotation = inspect.signature(fn).return_annotation to return_annotation = typing.get_type_hints(fn)["return"] which does resolve "dict_" and "dict" to dict. As a result, the block if return_annotation is inspect.Signature.empty can be replaced with if return_annotation is None. I assume that's preferable and make the change

kreczko avatar Mar 28 '25 17:03 kreczko

Thank you! This is great -- I've been meaning to make this change to everywhere in the codebase and new spots keep popping up in which it's needed.

Just checking -- does the change to test_adapters mean that this won't work if we don't use from __future__ import annotations?

elijahbenizzy avatar Mar 29 '25 17:03 elijahbenizzy

Just checking -- does the change to test_adapters mean that this won't work if we don't use from __future__ import annotations?

Good point, did not test this.

--- a/tests/function_modifiers/test_adapters.py
+++ b/tests/function_modifiers/test_adapters.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
pytest -s tests/function_modifiers/
======================================= 383 passed, 13 warnings in 3.21s =======================================

luckily same result.

Out of curiosity, I tried this example:

class B(dict):

    pass

def func() -> B:
    return B()

def func2() -> dict:
    return dict()

import inspect
import typing


print(inspect.signature(func).return_annotation)
print(inspect.signature(func2).return_annotation)

print(typing.get_type_hints(func)["return"])
print(typing.get_type_hints(func2)["return"])

without annotations they are identical

inspect: <class '__main__.B'>
inspect: <class 'dict'>
typing: <class '__main__.B'>
typing: <class 'dict'>

with from __future__ import annotations the inspect ones turn to string:

inspect: B
inspect: dict
typing: <class '__main__.B'>
typing: <class 'dict'>

Hope this helps

kreczko avatar Mar 31 '25 09:03 kreczko

@elijahbenizzy bump

skrawcz avatar May 16 '25 23:05 skrawcz