Repository: pydantic/pydantic

Issue:
Feature request -- no-alias constructor
# Feature Request -- no-alias constructor

Imagine the following model:

```py
from pydantic import BaseModel

class MyModel(BaseModel):
    my_field: int

    class Config:
        fields = {'my_field': {'alias': 'myField'}}
```

Now, I want to do the following:

* Deserialize input data, which is currently done as `MyModel(**data)`, where `data` is a dict like `{'myField': 1}`
* Create instances of `MyModel` using Python attribute names in order to serialize them, e.g. something like `MyModel(my_field=1).dict(by_alias=True)`
* And do the above two points without having to enable `allow_population_by_alias` to avoid introducing subtle bugs, as specified in the docs.

As far as I see, this could previously be done via `MyModel.construct(my_field=1)` but the signature of `construct` was changed so that this now leads to less clear code (`MyModel.construct({'my_field': 1}, {'my_field'})`).

I'm not sure yet how this could look in the API (maybe a new classmethod?), and I'm not sure that I'm not the only one concerned with this problem, but it would be great to hear from you on this.

Thanks in advance!


Discussion:
Surely just a subclass with only config alias changed?
@samuelcolvin That's a minimal example, of course there are more fields in the real world model, but aliases are the only thing relevant for the problem/idea :)
Right, but your subclass still only needs one thing.
@samuelcolvin Sorry I'm not sure what you mean by this :( The subclass has a defined field `my_field` and a defined config, and it works fine as expected in REPL. Am I missing something else?
Sorry, I'm traveling will try to help tomorrow.
```py
from pydantic import BaseModel

class MyModel(BaseModel):
    my_field: int

class MyModelWithAlias(MyModel):
    class Config:
        fields = {'my_field': {'alias': 'myField'}}

print(MyModelWithAlias(myField=123))
print(MyModel(my_field=123))
```

Outputs:

```
MyModelWithAlias my_field=123
MyModel my_field=123
```

So create the class, then create a sub-class with just `Config` where you set the aliases. Would this work for you?

This isn't exactly what I originally suggested, it turns out you can't create `MyModel` then a sub-class `MyModelWithoutAliases` where `Config.fields` is blanked.

Another approach would be to use [`create_model`](https://pydantic-docs.helpmanual.io/#dynamic-model-creation).
I'll try to be more clear on what I'm trying to achieve :)

My service must receive and produce in a format e.g. `{"myField": 1}`.

Receiving data is fine: I'm parsing it as `MyModel(**input_dict)` and get an object instance with attribute `my_field`.

The main question/problem is about producing data. I want to construct Python objects using Python attribute names in arguments (something which could look like `MyModel.make(my_field=1)`), and then serialize it into a dict using `.dict(by_alias=True)` and receive a dict with key `myField`.
Thus the solution with subclass would not work, because I cannot create an instance using Python attribute names and serialize that same instance with aliases as keys.
Okay, this is #477. "Dump aliases" feel free to comment there.

Also mentioned in #576.
@samuelcolvin By saying "dump aliases" do you imply that I'll still have to declare two model classes? One for loading data from external sources (with "usual" aliases), and another one for creating from attributes and dumping (with "dump" aliases), even though logically they represent the same schema. My thought was that it could fit in one class with an alternative constructor or factory method.
@m-burst I know you explicitly addressed this in your original post, but I think it might be worth revisiting one of the assumptions -- is `allow_population_by_alias` really that problematic? I know the pydantic docs have a big red warning about it, but as long as your aliases are just camelcased versions of the attributes (and they have non-pathological names), I'm having a hard time imagining a real-world scenario where the potential collision would actually cause problems. (Maybe I'm not thinking hard enough?)

Personally, I use the following `APIModel` as a base class for any model interfacing with external clients:
```python
import re

def snake2camel(snake: str, start_lower: bool = False) -> str:
    camel = snake.title()
    camel = re.sub("([0-9A-Za-z])_(?=[0-9A-Z])", lambda m: m.group(1), camel)
    if start_lower:
        camel = re.sub("(^_*[A-Z])", lambda m: m.group(1).lower(), camel)
    return camel

class APIModel(BaseModel):
    class Config(BaseConfig):
        allow_population_by_alias = True

        @classmethod
        def get_field_schema(cls, name: str) -> Dict[str, str]:
            """
            Generate a camelCase alias
            """
            field_config = super().get_field_schema(name) or {}
            if "alias" not in field_config:
                field_config["alias"] = snake2camel(name, start_lower=True)
            return field_config
```
Relying on a generated alias means I can be confident I'm not accidentally/carelessly manually generating collisions, and so far I have never run into a situation where converting to camel case could introduce ambiguity.

Using the above model means the service is always interfacing externally in camelcase, but I can always use snake case when writing python. (I'm not bothered by the fact that external clients can choose to use snake case if they want to, but maybe I should be?)
Alternatively, here's a relatively concise implementation of the `make` classmethod as I interpreted your description, with the same API as the usual `__init__` (except using the python attribute names), and the same validation errors:
```python
class APIModel2(BaseModel):
    @classmethod
    def make(cls, **data: Any):
        instance = cls.construct(values=data, fields_set=set(data.keys()))
        return cls(**instance.dict(by_alias=True))
```
As long as your aliases and attribute names don't conflict, I think this would behave properly.
@m-burst no I'm not suggesting two models.

I'm suggesting with have 3 alias options on a field:
* `alias` used for loading and dumping
* `load_alias` only used for loading, takes preference over `alias`
* `dump_alias` only used for dumping (`.dict()` and `.json()`), takes preference over `alias`

@dmontagu I agree about `allow_population_by_alias`, I think we should removing that warning or stop it being a warning, I think it unnecessarily upsets people.
I think the warning on `allow_population_by_alias` is fair because loading external data should be as strict as possible.

Suppose I have a RESTful API endpoint where clients JSON data in the body. If I enable `allow_population_by_alias`, clients using my API will be able to send requests that are incorrect (i.e. do not conform to schema) but will be processed correctly, and neither the clients nor I will know that there is a mistake. And in this situation I won't be able to disable `allow_population_by_alias` ever because technically it can break the clients (and I may want to disable it at any time because the reasons I enabled it are purely internal to the backend).

@dmontagu The idea of `APIModel2` is exactly what I was looking for, though there is obviously a lot of room for performance optimization, and I just thought it made sense to have a built-in and more optimized implementation of this in pydantic itself.

@samuelcolvin I still don't understand how load/dump aliases are the answer to my question. To put it simply, I need to construct the same model with and without aliases while specifying explicitly what the keys are, and I don't see how load/dump aliases will allow me to do everything I need in one model.
ye, I see that. I was just explaining my thoughts on improvements to aliases.

Can't you just use something like

```py
class MyModelInternalVersion(MyModel):
    class Config:
        allow_population_by_alias = True
```
Then use that second model when you need to reload the model?

As a proper solution, I have a hard rule that `__init__` should not have any special kwargs, but we could change `parse_obj` to include a `by_field_name=False` argument, when `True` names work as well as aliases. Would that help? 

We might want more options, eg.:
* only by alias - default
* alias or name
* only name

?

We should also rename `allow_population_by_alias` to `allow_population_by_field_name` or something, the current name is dumb.

suggestions of other names for both welcome.

Resolving patch:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -56,7 +56,7 @@ class BaseConfig:
     validate_all = False
     extra = Extra.ignore
     allow_mutation = True
-    allow_population_by_alias = False
+    allow_population_by_field_name = False
     use_enum_values = False
     fields: Dict[str, Union[str, Dict[str, str]]] = {}
     validate_assignment = False
@@ -104,13 +104,20 @@ def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType') -> 'C
 EXTRA_LINK = 'https://pydantic-docs.helpmanual.io/usage/model_config/'
 
 
-def set_extra(config: Type[BaseConfig], cls_name: str) -> None:
+def prepare_config(config: Type[BaseConfig], cls_name: str) -> None:
     if not isinstance(config.extra, Extra):
         try:
             config.extra = Extra(config.extra)
         except ValueError:
             raise ValueError(f'"{cls_name}": {config.extra} is not a valid value for "extra"')
 
+    if hasattr(config, 'allow_population_by_alias'):
+        warnings.warn(
+            f'{cls_name}: "allow_population_by_alias" is deprecated and replaced by "allow_population_by_field_name"',
+            DeprecationWarning,
+        )
+        config.allow_population_by_field_name = config.allow_population_by_alias  # type: ignore
+
 
 def is_valid_field(name: str) -> bool:
     if not name.startswith('_'):
@@ -155,7 +162,7 @@ def __new__(mcs, name, bases, namespace):
                 # re-run prepare to add extra validators
                 f.prepare()
 
-        set_extra(config, name)
+        prepare_config(config, name)
 
         class_vars = set()
         if (namespace.get('__module__'), namespace.get('__qualname__')) != ('pydantic.main', 'BaseModel'):
@@ -728,7 +735,7 @@ def validate_model(  # noqa: C901 (ignore complexity)
 
         value = input_data.get(field.alias, _missing)
         using_name = False
-        if value is _missing and config.allow_population_by_alias and field.alt_alias:
+        if value is _missing and config.allow_population_by_field_name and field.alt_alias:
             value = input_data.get(field.name, _missing)
             using_name = True
 


Test patch:
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -786,7 +786,7 @@ class Model(BaseModel):
 
         class Config:
             extra = Extra.forbid
-            allow_population_by_alias = True
+            allow_population_by_field_name = True
             fields = {'last_updated_by': 'lastUpdatedBy'}
 
     assert Model(lastUpdatedBy='foo').dict() == {'last_updated_by': 'foo'}
@@ -846,25 +846,25 @@ class Config:
 
     class Child(Mixin, Parent):
         class Config:
-            allow_population_by_alias = True
+            allow_population_by_field_name = True
 
     assert BaseModel.__config__.allow_mutation is True
-    assert BaseModel.__config__.allow_population_by_alias is False
+    assert BaseModel.__config__.allow_population_by_field_name is False
     assert BaseModel.__config__.extra is Extra.ignore
     assert BaseModel.__config__.use_enum_values is False
 
     assert Parent.__config__.allow_mutation is False
-    assert Parent.__config__.allow_population_by_alias is False
+    assert Parent.__config__.allow_population_by_field_name is False
     assert Parent.__config__.extra is Extra.forbid
     assert Parent.__config__.use_enum_values is False
 
     assert Mixin.__config__.allow_mutation is True
-    assert Mixin.__config__.allow_population_by_alias is False
+    assert Mixin.__config__.allow_population_by_field_name is False
     assert Mixin.__config__.extra is Extra.ignore
     assert Mixin.__config__.use_enum_values is True
 
     assert Child.__config__.allow_mutation is False
-    assert Child.__config__.allow_population_by_alias is True
+    assert Child.__config__.allow_population_by_field_name is True
     assert Child.__config__.extra is Extra.forbid
     assert Child.__config__.use_enum_values is True
 
@@ -1015,3 +1015,19 @@ def test_scheme_deprecated():
 
         class Model(BaseModel):
             foo: int = Schema(4)
+
+
+def test_population_by_alias():
+    with pytest.warns(DeprecationWarning, match='"allow_population_by_alias" is deprecated and replaced by'):
+
+        class Model(BaseModel):
+            a: str
+
+            class Config:
+                allow_population_by_alias = True
+                fields = {'a': {'alias': '_a'}}
+
+    assert Model.__config__.allow_population_by_field_name is True
+    assert Model(a='different').a == 'different'
+    assert Model(a='different').dict() == {'a': 'different'}
+    assert Model(a='different').dict(by_alias=True) == {'_a': 'different'}
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -292,12 +292,12 @@ class Config:
     }
 
 
-def test_population_by_alias():
+def test_population_by_field_name():
     class Model(BaseModel):
         a: str
 
         class Config:
-            allow_population_by_alias = True
+            allow_population_by_field_name = True
             fields = {'a': {'alias': '_a'}}
 
     assert Model(a='different').a == 'different'
@@ -784,7 +784,7 @@ class MyModel(BaseModel):
         b: str = Field('default', alias='alias_b')
 
         class Config:
-            allow_population_by_alias = True
+            allow_population_by_field_name = True
 
     m = MyModel(alias_a='a')