Fix dict annotations#3123
Conversation
Signed-off-by: Yee Hing Tong <wild-endeavor@users.noreply.github.com>
Code Review Agent Run Status
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #3123 +/- ##
===========================================
+ Coverage 79.60% 92.75% +13.15%
===========================================
Files 203 7 -196
Lines 21625 497 -21128
Branches 2788 0 -2788
===========================================
- Hits 17214 461 -16753
+ Misses 3629 36 -3593
+ Partials 782 0 -782 ☔ View full report in Codecov by Sentry. |
eapolinario
left a comment
There was a problem hiding this comment.
this looks good. Left a few nits, but won't block on any of them.
| _, v_type = transformer_override.extract_types_or_metadata(t_value_type) # type: ignore | ||
| else: | ||
| _, v_type = DictTransformer.extract_types_or_metadata(t_value_type) # type: ignore | ||
| _, v_type = DictTransformer.extract_types(t_value_type) # type: ignore |
There was a problem hiding this comment.
can we remove the # type: ignore now?
| @staticmethod | ||
| def is_pickle(python_type: Type[dict]) -> typing.Tuple[bool, Type]: | ||
| base_type, *metadata = DictTransformer.extract_types_or_metadata(python_type) | ||
| def is_pickle(python_type: Type[dict]) -> bool: |
There was a problem hiding this comment.
the change of return type in is_pickle is the best change in this PR.
| @pytest.mark.parametrize( | ||
| "t,expected", | ||
| [ | ||
| (None, (None, None)), | ||
| (typing.Dict, ()), | ||
| (typing.Dict[str, str], (str, str)), | ||
| ( | ||
| Annotated[typing.Dict[str, str], kwtypes(allow_pickle=True)], | ||
| (typing.Dict[str, str], kwtypes(allow_pickle=True)), | ||
| ), | ||
| (typing.Dict[Annotated[str, "a-tag"], int], (Annotated[str, "a-tag"], int)), | ||
| ], | ||
| ) | ||
| def test_dict_get(t, expected): | ||
| assert DictTransformer.extract_types_or_metadata(t) == expected | ||
|
|
||
|
|
Code Review Agent Run #d12db3Actionable Suggestions - 5
Review Details
|
Changelist by BitoThis pull request implements the following key changes.
|
|
|
||
| if isinstance(val, dict): | ||
| ktype, vtype = DictTransformer.extract_types_or_metadata(t) | ||
| ktype, vtype = DictTransformer.extract_types(t) |
There was a problem hiding this comment.
Consider keeping the extract_types_or_metadata method call instead of changing to extract_types. The original method may handle metadata that could be important for dictionary type transformations.
Code suggestion
Check the AI-generated fix before applying
| ktype, vtype = DictTransformer.extract_types(t) | |
| ktype, vtype = DictTransformer.extract_types_or_metadata(t) |
Code Review Run #d12db3
Is this a valid issue, or was it incorrectly flagged by the Agent?
- it was incorrectly flagged
Signed-off-by: Yee Hing Tong <wild-endeavor@users.noreply.github.com>
Code Review Agent Run #bebaf5Actionable Suggestions - 0Review Details
|
| if _origin is dict and _args is not None: | ||
| return _args # type: ignore | ||
| elif _origin is Annotated: | ||
| return DictTransformer.extract_types(_args[0]) | ||
| else: | ||
| raise ValueError(f"Trying to extract dictionary type information from a non-dict type {t}") |
There was a problem hiding this comment.
Just use debugger to run the unit test of this PR, this make sense to me.
There was a problem hiding this comment.
checking other usecase in annotation, put tuple in it
There was a problem hiding this comment.
oh I see, tuple type is restricted now
There was a problem hiding this comment.
the if else statement in this function looks super weird, trying to fix it
|
|
||
|
|
There was a problem hiding this comment.
Let's add test when Tuple IDL is supported!
flyteorg/flyte#5699
def test_pydantic_annotated_tuple():
from pydantic import BaseModel
from flytekit.core.artifact import Artifact
from flytekit import task, dynamic
from flytekit.configuration import SerializationSettings, ImageConfig, Image
from flytekit.core.context_manager import FlyteContextManager, ExecutionState, FlyteContext
from typing import Annotated, Union, Tuple
# Local Pydantic model for demonstration
class Config2(BaseModel):
param: int = 42
@task
def get_config() -> Config2:
return Config2()
# We'll reuse the Prophet class already defined above in the same file.
# If needed, ensure it's defined at a higher scope or imported in your actual code.
def dt_function():
# Create Pydantic object
cfg = get_config()
# Create a dummy Prophet dict
best_models = {"model_1": Prophet(param=0.1)}
# Return a tuple of (Config2, dict[str, Prophet])
return cfg, best_models
a = Artifact(name="my_tuple_model")
# A dynamic workflow returning an Annotated tuple
@dynamic
def dt_tuple() -> Annotated[Tuple[Config2, dict[str, Prophet]], a]:
return dt_function()
# Serialization settings similar to other tests
ss = SerializationSettings(
project="test_proj",
domain="test_domain",
version="abc",
image_config=ImageConfig(Image(name="name", fqn="image", tag="name")),
env={},
)
# Compile the dynamic workflow into a spec and ensure it behaves as expected
with FlyteContextManager.with_context(
FlyteContextManager.current_context().with_serialization_settings(ss)
) as ctx:
new_exc_state = ctx.execution_state.with_params(mode=ExecutionState.Mode.TASK_EXECUTION)
with FlyteContextManager.with_context(ctx.with_execution_state(new_exc_state)):
dynamic_job_spec_tuple = dt_tuple.compile_into_workflow(ctx, dt_tuple._task_function)
# We expect exactly one node in the spec (this dynamic call itself),
# and a single output binding containing our tuple
assert len(dynamic_job_spec_tuple.nodes) == 1
assert len(dynamic_job_spec_tuple.outputs) == 1
# Here, we can check that the output is stored as a collection of two items
bd = dynamic_job_spec_tuple.outputs[0].binding
assert bd.collection is not None
assert len(bd.collection.bindings) == 2Signed-off-by: Yee Hing Tong <wild-endeavor@users.noreply.github.com> Signed-off-by: Chih Tsung Lu <lu001224@gmail.com>
Signed-off-by: Yee Hing Tong <wild-endeavor@users.noreply.github.com> Signed-off-by: Atharva <atharvakulkarni172003@gmail.com>
Tracking issue
Internal ticket only.
Why are the changes needed?
Customer reported issue with internal model registry. The issue though is that annotations don't work well with dictionary return types.
What changes were proposed in this pull request?
Most of the changes here are in the dict transformer.
extract_types_or_metadatafunction is inconsistent, returning different things in different scenarios - sometimes it would return annotations, sometimes it would return the key/value type of a dictionary. This has been changed to always return the latter, or an empty tuple()in the case of an untyped dictionary. In all cases now, the caller interprets the result as a tuple of key type,value type. Renaming function to justextract_types.is_picklefunction returned both a boolean, as well as types, but the latter isn't really necessary. Changing this to just return the pickle bool.dict_typesis a function that was almost the same asextract_types_or_metadata, removing in favor of that one and manually extracting the annotation args where needed.How was this patch tested?
Setup process
Screenshots
Check all the applicable boxes
Related PRs
Docs link
Summary by Bito
Enhanced DictTransformer implementation by refactoring type handling and simplifying type extraction functionality. The extract_types_or_metadata function has been renamed to extract_types, with improved type casting. Reorganized test structure by eliminating duplicate tests and properly relocating dictionary type extraction tests. Changes improve consistency in dictionary type handling and focus on type safety improvements.Unit tests added: True
Estimated effort to review (1-5, lower is better): 2