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

How to rename attributes and still support the old name of it? #417

Open
tadas-subonis opened this issue Aug 1, 2018 · 14 comments
Open
Labels

Comments

@tadas-subonis
Copy link

The case that I ran into was that I needed to rename a variable but I still would like to support the old JSON that used the old name.

Basically, I am looking for some kind of hook if the old attribute is supplied, use it for the new name, but do not use that old name anywhere else. I mostly use cattrs to do the loading/dumping of the classes.

@hynek
Copy link
Member

hynek commented Aug 1, 2018

Do you need it for init or just access? You could solve access easily by adding a property that returns the value of the new name.

@tadas-subonis
Copy link
Author

For init

@chobeat
Copy link
Contributor

chobeat commented Aug 1, 2018

you should then use your new name as an attribute and create a property with the old one, right?

@tadas-subonis
Copy link
Author

tadas-subonis commented Aug 1, 2018

The problem is that the old JSON has the old attribute. I load the JSON to the dict and then I use the dict with cattrs to create the attr annotated class. Normally I would modify the __init__ to accept the new and the old attributes but I do not think that I can do that with attrs here. Or can I?

@hynek
Copy link
Member

hynek commented Aug 1, 2018

This sounds like a feature for cattrs rather than attrs TBH? cc @Tinche

@tadas-subonis
Copy link
Author

@hynek It would feel more like attrs as I am trying to make the old and the new interfaces of the class compatible. Something like an 'alias' for attr.ib would be great here IMO.

@Tinche
Copy link
Member

Tinche commented Aug 6, 2018

This sounds more like a problem for cattrs than attrs.

I would just write a loading function manually that checks the dict for the old value and register it as a structure hook in cattrs.

@tadas-subonis
Copy link
Author

Think about this use case - you update a Python lib that you develop and that uses the attrs. You update it by changing some attribute name. This would break the old clients (users) of the lib as they won't be able to initialize a class with the old attribute name.

Normally you would write init (that accepts the old name) and add a @Property getter so it wouldn't break for the users.

cattrs and dict and json is just one of the use cases.

@wsanchez
Copy link

wsanchez commented Aug 6, 2018

Right, but you phrased the initial description as a JSON problem. :-)

Mapping JSON schema names to python object attributes is what's not attrs' problem.

But I think I understand that there could be a feature for keyword argument aliases in the generated __init__. I suggest you open a new ticket describing that use case, so the above JSON baggage doesn't come along for the ride.

@asford
Copy link
Contributor

asford commented Aug 12, 2018

I'm kibitzing, but the solution proposed in #393 seems relevant.

Support for a user-defined __init__ with a generated __attrs_init__ would provide a solid escape hatch for the breaking-rename scenario you've described.

@TomGoBravo
Copy link

I have the same problem as the original post. I'm using cattrs to populate attrs classes from JSON. Some of the JSON keys have different names from the class attributes. https://pypi.org/project/related/ uses metadata({'key': 'json_name'}) but i'd like to stick to cattrs and attrs. I tried following @Tinche 's suggestion of calling register_structure_hook with a function that remaps the keys but I'd need to copy all the logic of Converter.structure_attrs_fromdict to handle nested attrs attribuets. I'm going to try recursively walking the input JSON to rename the keys before passing it to cattrs.

@ericbn
Copy link

ericbn commented Oct 13, 2021

Maybe the solution is to have two separate converters: one for the old attribute names, and one for the new ones.

import json

import attr
from cattr.gen import make_dict_structure_fn, override
from cattr.preconf.json import make_converter


@attr.define
class Person:
    full_name: str


old_converter = make_converter()
old_converter.register_structure_hook(Person, make_dict_structure_fn(
    Person, old_converter, full_name=override(rename='fullName')))
old_json = '{"fullName": "John Doe"}'
old_converter.structure(json.loads(old_json), Person)
# Person(full_name='John Doe')

new_converter = make_converter()
new_json = '{"full_name": "John Doe"}'
new_converter.structure(json.loads(new_json), Person)
# Person(full_name='John Doe')

@ericbn
Copy link

ericbn commented Oct 13, 2021

And @TomGoBravo, for nested attributes, which I'm assuming come from a composition of objects of different classes, you can register converter hooks for each separate class:

import json

import attr
from cattr.gen import make_dict_structure_fn, override
from cattr.preconf.json import make_converter


@attr.define
class Job:
    role: str


@attr.define
class Person:
    full_name: str
    job: Job


old_converter = make_converter()
old_converter.register_structure_hook(Job, make_dict_structure_fn(
    Job, old_converter, role=override(rename='roleName')))
old_converter.register_structure_hook(Person, make_dict_structure_fn(
    Person, old_converter, full_name=override(rename='fullName')))
old_json = '{"fullName": "Roger Wilco", "job": {"roleName": "janitor"}}'
old_converter.structure(json.loads(old_json), Person)
# Person(full_name='Roger Wilco', job=Job(role='janitor'))

@nrbnlulu
Copy link

nrbnlulu commented May 25, 2022

what @ericbn suggested have runtime penalty the is not nececery if the the new attributed was added to __init__

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

No branches or pull requests

9 participants