Skip to content

Commit

Permalink
Fix typeddicts with period fields (#377)
Browse files Browse the repository at this point in the history
* Fix typeddicts with period fields

* Tweaks
  • Loading branch information
Tinche authored Jun 7, 2023
1 parent d1368b2 commit dedd72d
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 18 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# History

## 23.2.0 (UNRELEASED)

- Use [PDM](https://pdm.fming.dev/latest/) instead of Poetry.
- _cattrs_ is now linted with [Ruff](https://beta.ruff.rs/docs/).
- Fix TypedDicts with periods in their field names.
([#376](https://github.com/python-attrs/cattrs/issues/376) [#377](https://github.com/python-attrs/cattrs/pull/377))

## 23.1.2 (2023-06-02)

- Improve `typing_extensions` version bound. ([#372](https://github.com/python-attrs/cattrs/issues/372))

## 23.1.1 (2023-05-30)

- Add `typing_extensions` as a direct dependency on 3.10. ([#369](https://github.com/python-attrs/cattrs/issues/369) [#370](https://github.com/python-attrs/cattrs/pull/370))

## 23.1.0 (2023-05-30)
Expand Down
36 changes: 19 additions & 17 deletions src/cattrs/gen/typeddicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def make_dict_unstructure_fn(
# We've not broken the loop.
return converter._unstructure_identity

for a in attrs:
for ix, a in enumerate(attrs):
attr_name = a.name
override = kwargs.get(attr_name, neutral)
if override.omit:
Expand Down Expand Up @@ -190,7 +190,7 @@ def make_dict_unstructure_fn(
is_identity = handler == converter._unstructure_identity

if not is_identity:
unstruct_handler_name = f"__c_unstr_{attr_name}"
unstruct_handler_name = f"__c_unstr_{ix}"
globs[unstruct_handler_name] = handler
internal_arg_parts[unstruct_handler_name] = handler
invoke = f"{unstruct_handler_name}(instance['{attr_name}'])"
Expand Down Expand Up @@ -315,7 +315,7 @@ def make_dict_structure_fn(
lines.append(" errors = []")
internal_arg_parts["__c_cve"] = ClassValidationError
internal_arg_parts["__c_avn"] = AttributeValidationNote
for a in attrs:
for ix, a in enumerate(attrs):
an = a.name
attr_required = an in req_keys
override = kwargs.get(an, neutral)
Expand All @@ -340,7 +340,7 @@ def make_dict_structure_fn(
else:
handler = find_structure_handler(a, t, converter)

struct_handler_name = f"__c_structure_{an}"
struct_handler_name = f"__c_structure_{ix}"
internal_arg_parts[struct_handler_name] = handler

kn = an if override.rename is None else override.rename
Expand All @@ -351,15 +351,17 @@ def make_dict_structure_fn(
i = f"{i} "
lines.append(f"{i}try:")
i = f"{i} "
type_name = f"__c_type_{an}"
internal_arg_parts[type_name] = t

tn = f"__c_type_{ix}"
internal_arg_parts[tn] = t

if handler:
if handler == converter._structure_call:
internal_arg_parts[struct_handler_name] = t
lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'])")
else:
lines.append(
f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {type_name})"
f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})"
)
else:
lines.append(f"{i}res['{an}'] = o['{kn}']")
Expand All @@ -369,7 +371,7 @@ def make_dict_structure_fn(
lines.append(f"{i}except Exception as e:")
i = f"{i} "
lines.append(
f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]'
f'{i}e.__notes__ = [*getattr(e, \'__notes__\', []), __c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", {tn})]'
)
lines.append(f"{i}errors.append(e)")

Expand All @@ -387,14 +389,14 @@ def make_dict_structure_fn(
non_required = []

# The first loop deals with required args.
for a in attrs:
for ix, a in enumerate(attrs):
an = a.name
attr_required = an in req_keys
override = kwargs.get(an, neutral)
if override.omit:
continue
if not attr_required:
non_required.append(a)
non_required.append((ix, a))
continue

t = a.type
Expand All @@ -419,18 +421,18 @@ def make_dict_structure_fn(
allowed_fields.add(kn)

if handler:
struct_handler_name = f"__c_structure_{an}"
struct_handler_name = f"__c_structure_{ix}"
internal_arg_parts[struct_handler_name] = handler
if handler == converter._structure_call:
internal_arg_parts[struct_handler_name] = t
invocation_line = (
f" res['{an}'] = {struct_handler_name}(o['{kn}'])"
)
else:
type_name = f"__c_type_{an}"
internal_arg_parts[type_name] = t
tn = f"__c_type_{ix}"
internal_arg_parts[tn] = t
invocation_line = (
f" res['{an}'] = {struct_handler_name}(o['{kn}'], {type_name})"
f" res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})"
)
else:
invocation_line = f" res['{an}'] = o['{kn}']"
Expand All @@ -441,7 +443,7 @@ def make_dict_structure_fn(

# The second loop is for optional args.
if non_required:
for a in non_required:
for ix, a in non_required:
an = a.name
override = kwargs.get(an, neutral)
t = a.type
Expand All @@ -463,7 +465,7 @@ def make_dict_structure_fn(
else:
handler = converter.structure

struct_handler_name = f"__c_structure_{an}"
struct_handler_name = f"__c_structure_{ix}"
internal_arg_parts[struct_handler_name] = handler

ian = an
Expand All @@ -477,7 +479,7 @@ def make_dict_structure_fn(
f" res['{ian}'] = {struct_handler_name}(o['{kn}'])"
)
else:
tn = f"__c_type_{an}"
tn = f"__c_type_{ix}"
internal_arg_parts[tn] = t
post_lines.append(
f" res['{ian}'] = {struct_handler_name}(o['{kn}'], {tn})"
Expand Down
14 changes: 13 additions & 1 deletion tests/typeddicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@
T3 = TypeVar("T3")


def gen_typeddict_attr_names():
"""Typed dicts can have periods in their field names."""
counter = 0
for n in gen_attr_names():
counter += 1

if counter % 2 == 0:
n = f"{n}.suffix"

yield n


@composite
def int_attributes(
draw: DrawFn, total: bool = True, not_required: bool = False
Expand Down Expand Up @@ -112,7 +124,7 @@ def simple_typeddicts(
)
)

attrs_dict = {n: attr[0] for n, attr in zip(gen_attr_names(), attrs)}
attrs_dict = {n: attr[0] for n, attr in zip(gen_typeddict_attr_names(), attrs)}
success_payload = {}
for n, a in zip(attrs_dict, attrs):
v = draw(a[1])
Expand Down

0 comments on commit dedd72d

Please sign in to comment.