Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to support attr.ib(init=False) ? #40

Closed
mooncake4132 opened this issue Aug 21, 2018 · 19 comments
Closed

Possible to support attr.ib(init=False) ? #40

mooncake4132 opened this issue Aug 21, 2018 · 19 comments
Assignees
Milestone

Comments

@mooncake4132
Copy link

  • cattrs version: 0.9.0
  • Python version: 3.7.0
  • Operating System: Windows

Description

Right now cattr can only convert dictionaries back to the respective attr object if the field can be initialized in __init__ (because it calls cl(**conv_obj)). Is it possible for cattr to work with fields with init set to False?

What I Did

Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import attr
>>> import cattr
>>> @attr.s
... class A:
...     a: int = attr.ib(init=False)
...
>>> a = A()
>>> a.a = 10
>>> s = cattr.unstructure(a)
>>> cattr.structure(s, A)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\.virtualenvs\test\lib\site-packages\cattr\converters.py", line 178, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
  File "D:\.virtualenvs\test\lib\site-packages\cattr\converters.py", line 298, in structure_attrs_fromdict
    return cl(**conv_obj)
TypeError: __init__() got an unexpected keyword argument 'a'
>>>
@asford
Copy link
Contributor

asford commented Mar 17, 2021

Also interested in a fix for this.

@Tinche Would by-passing init=False fields on both structure and unstructure make sense to you?

@Tinche
Copy link
Member

Tinche commented Mar 17, 2021

@asford The fix needs to be here: https://github.com/Tinche/cattrs/blob/161dbd7b0ebd36123480ea770efab1929b2e57c6/src/cattr/gen.py#L151 (I guess just skipping attributes which are init=False).

@Tinche
Copy link
Member

Tinche commented Mar 22, 2021

I might start on this soonish. But what should the spec be? If the key is present in the payload, use a setter instead of the initializer? Or should we just ignore fields with init=False completely when structuring?

@wakemaster39
Copy link

I vote for just skip, I heavily use and abuse frozen=True so a setter would explode if I managed to get into that situation. Could this somehow move into an option for the new GenConverter stuff if you wanted flexibility?

@Tinche
Copy link
Member

Tinche commented Mar 23, 2021

Yeah I agree, let's skip for now and if there's demand we can revisit later with an option.

@asford
Copy link
Contributor

asford commented Mar 23, 2021 via email

@Tinche
Copy link
Member

Tinche commented Mar 23, 2021

Just out of curiosity, what do you folks use init=False fields for? Computed attributes set in post init?

@wakemaster39
Copy link

wakemaster39 commented Mar 23, 2021

constants, if I type something with auto_attribs=True set it automatically becomes able to be set during initialization. So I have to use init=False to ensure it is not mutable until frozen=True takes over and protects it.

I use it very rarely for this reason, but its the only one I have had so far.

@Tinche
Copy link
Member

Tinche commented Mar 23, 2021

Hm, can you give an example? Your use case sounds like a ClassVar would be appropriate?

@wakemaster39
Copy link

oh 1000% a class var does the same thing. I don't use it often so I usually remember to just do and forget I should do something else.

@attrs(auto_attribs=True, frozen=True)
class Test:
    a: str
    b: str = attrib(init=False, default="QQ")

@swuecho
Copy link

swuecho commented Mar 25, 2021

Just out of curiosity, what do you folks use init=False fields for? Computed attributes set in post init?

Yes. I have a transpiler, most of component's attr are computed and are optional

@jpsnyder
Copy link

I just found this issue because I'm running into this exact problem.

I use init=False attributes to create a "type" field to help with serialization. I don't want users to be able to change it, but cattrs seems to complain when it is included.
Had to resort to creating a special hook to remove the "type" field after determining the appropriate class.

@Tinche
Copy link
Member

Tinche commented Mar 26, 2021

@jpsnyder Interesting. Could you remove the field and create an unstructuring hook that adds it, for serialization?

@jpsnyder
Copy link

It looks like I don't need to have the type field anymore using your example in #140 Thanks again!

However, I would still vote for supporting init=False. I also had a use for it when I wanted to allow users to add "tags" to their constructed object, but didn't want to expose the tags in the init. However, this is purely a design decision in an effort to separate the two ideas with syntactic sugar.

@attr.define
class Element:
    tags: Set[str] = attr.ib(init=False, factory=set)

    def add_tag(self, *tags: Iterable[str]):
        for tag in tags:
            self.tags.add(tag)
        return self
        
 @attr.define
 class SubOne(Element):
     a: int
        
obj = SubOne(1).add_tag("apple", "banana")

@kaxil
Copy link
Contributor

kaxil commented Sep 2, 2022

Any update on this?

@attr.define()
class Dataset:
    """A Dataset is used for marking data dependencies between workflows."""

    uri: str
    extra: Optional[Dict[str, Any]] = None


@define()
class File(Dataset):
    path: str
    conn_id: Optional[str] = None
    filetype: Optional[constants.FileType] = None
    normalize_config: Optional[Dict] = None

    uri: str = field(init=False, repr=False)
    extra: Optional[Dict] = field(init=False, factory=dict, repr=False)

This fails with :

  |   File "/usr/local/lib/python3.9/site-packages/airflow/lineage/__init__.py", line 75, in _render_object
  |     return structure(
  |   File "/usr/local/lib/python3.9/site-packages/cattrs/converters.py", line 281, in structure
  |     return self._structure_func.dispatch(cl)(obj, cl)
  |   File "<cattrs generated structure astro.files.base.File>", line 44, in structure_File
  |     except Exception as exc: raise __c_cve('While structuring File', [exc], __cl)
  | cattrs.errors.ClassValidationError: While structuring File (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<cattrs generated structure astro.files.base.File>", line 41, in structure_File
    |     return __cl(
    | TypeError: __init__() got an unexpected keyword argument 'uri'
    +------------------------------------

@AdrianSosic
Copy link
Contributor

Hi, I would be also interested in this feature. To answer your question, @Tinche:

Just out of curiosity, what do you folks use init=False fields for? Computed attributes set in post init?

Yes, this is my primary use case. I think there are many situations where post init is required for a clean code structure and it would be awesome if cattrs would handle these situations automatically (because I think the only other thing someone would do in this situation is to take the boilerplate route and add the trivial structure hooks manually).

Or is there any better way to achieve the same with the current capabilities?

@coreegor
Copy link

coreegor commented Jul 5, 2023

Hello ! Any update on this?

@Tinche Tinche added this to the 23.2 milestone Jul 5, 2023
@Tinche Tinche self-assigned this Jul 5, 2023
@Tinche
Copy link
Member

Tinche commented Jul 5, 2023

It'll be in the next milestone, alongside new attrs aliases!

@Tinche
Copy link
Member

Tinche commented Jul 17, 2023

Aaaand merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants