mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
allow aliases
This commit is contained in:
@@ -25,10 +25,13 @@ class BaseSettings(BaseModel):
|
||||
"""
|
||||
d = {}
|
||||
for name, field in self.__fields__.items():
|
||||
env_name = self.config.env_prefix + field.name.upper()
|
||||
if field.alt_alias:
|
||||
env_name = field.alias
|
||||
else:
|
||||
env_name = self.config.env_prefix + field.name.upper()
|
||||
env_var = os.getenv(env_name, None)
|
||||
if env_var:
|
||||
d[name] = env_var
|
||||
d[field.alias] = env_var
|
||||
return d
|
||||
|
||||
class Config:
|
||||
|
||||
+15
-7
@@ -21,13 +21,14 @@ class Shape(IntEnum):
|
||||
|
||||
|
||||
class Field:
|
||||
__slots__ = ('type_', 'key_type_', 'sub_fields', 'key_field', 'validators', 'default',
|
||||
'required', 'name', 'description', 'info', 'validate_always', 'allow_none', 'shape', 'multipart')
|
||||
__slots__ = ('type_', 'key_type_', 'sub_fields', 'key_field', 'validators', 'default', 'required',
|
||||
'name', 'alias', 'description', 'info', 'validate_always', 'allow_none', 'shape', 'multipart')
|
||||
|
||||
def __init__(
|
||||
self, *,
|
||||
name: str=None,
|
||||
name: str,
|
||||
type_: Type,
|
||||
alias: str=None,
|
||||
class_validators: dict=None,
|
||||
default: Any=None,
|
||||
required: bool=False,
|
||||
@@ -35,6 +36,7 @@ class Field:
|
||||
description: str=None):
|
||||
|
||||
self.name: str = name
|
||||
self.alias: str = alias or name
|
||||
self.type_: type = type_
|
||||
self.key_type_: type = None
|
||||
self.validate_always: bool = getattr(self.type_, 'validate_always', False)
|
||||
@@ -51,16 +53,22 @@ class Field:
|
||||
self._prepare(class_validators or {})
|
||||
|
||||
@classmethod
|
||||
def infer(cls, *, name, value, annotation, class_validators):
|
||||
def infer(cls, *, name, value, annotation, class_validators, field_config):
|
||||
required = value == Ellipsis
|
||||
return cls(
|
||||
name=name,
|
||||
type_=annotation,
|
||||
alias=field_config and field_config.get('alias'),
|
||||
class_validators=class_validators,
|
||||
default=None if required else value,
|
||||
required=required,
|
||||
name=name
|
||||
description=field_config and field_config.get('description'),
|
||||
)
|
||||
|
||||
@property
|
||||
def alt_alias(self):
|
||||
return self.name != self.alias
|
||||
|
||||
def _prepare(self, class_validators):
|
||||
if self.default is not None and self.type_ is None:
|
||||
self.type_ = type(self.default)
|
||||
@@ -134,9 +142,9 @@ class Field:
|
||||
name=f'key_{self.name}'
|
||||
)
|
||||
|
||||
if self.sub_fields is None and getattr(self.type_, '__origin__', None):
|
||||
if self.sub_fields is None and getattr(self.type_, '__origin__', False):
|
||||
self.multipart = True
|
||||
self.sub_fields = self.sub_fields or [Field(
|
||||
self.sub_fields = [Field(
|
||||
type_=self.type_,
|
||||
class_validators=class_validators,
|
||||
default=self.default,
|
||||
|
||||
+12
-6
@@ -15,6 +15,7 @@ class BaseConfig:
|
||||
validate_all = False
|
||||
ignore_extra = True
|
||||
allow_extra = False
|
||||
fields = None
|
||||
|
||||
|
||||
def inherit_config(self_config, parent_config) -> BaseConfig:
|
||||
@@ -47,14 +48,19 @@ class MetaModel(type):
|
||||
class_validators = {n: f for n, f in namespace.items()
|
||||
if n.startswith('validate_') and isinstance(f, FunctionType)}
|
||||
|
||||
config_fields = config.fields or {}
|
||||
for var_name, value in namespace.items():
|
||||
if var_name.startswith('_') or isinstance(value, TYPE_BLACKLIST):
|
||||
continue
|
||||
field_config = config_fields.get(var_name)
|
||||
if isinstance(field_config, str):
|
||||
field_config = {'alias': field_config}
|
||||
field = Field.infer(
|
||||
name=var_name,
|
||||
value=value,
|
||||
annotation=annotations and annotations.get(var_name),
|
||||
class_validators=class_validators,
|
||||
field_config=field_config,
|
||||
)
|
||||
fields[var_name] = field
|
||||
namespace.update(
|
||||
@@ -112,11 +118,11 @@ class BaseModel(metaclass=MetaModel):
|
||||
|
||||
def _process_values(self, values):
|
||||
for name, field in self.__fields__.items():
|
||||
value = values.get(name, MISSING)
|
||||
self._process_value(name, field, value)
|
||||
value = values.get(field.alias, MISSING)
|
||||
self._process_value(name, field.alias, field, value)
|
||||
|
||||
if not self.config.ignore_extra or self.config.allow_extra:
|
||||
extra = values.keys() - self.__fields__.keys()
|
||||
extra = values.keys() - {f.alias for f in self.__fields__.values()}
|
||||
if extra:
|
||||
if self.config.allow_extra:
|
||||
for field in extra:
|
||||
@@ -131,13 +137,13 @@ class BaseModel(metaclass=MetaModel):
|
||||
if self.config.raise_exception and self.__errors__:
|
||||
raise ValidationError(self.__errors__)
|
||||
|
||||
def _process_value(self, name, field, value):
|
||||
def _process_value(self, name, alias, field, value):
|
||||
if value is MISSING:
|
||||
if self.config.validate_all or field.validate_always:
|
||||
value = field.default
|
||||
else:
|
||||
if field.required:
|
||||
self.__errors__[name] = MISSING_ERROR
|
||||
self.__errors__[alias] = MISSING_ERROR
|
||||
else:
|
||||
self.__values__[name] = field.default
|
||||
# could skip this if the attributes equals field.default, would it be quicker?
|
||||
@@ -146,7 +152,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
|
||||
value, errors = field.validate(value, self)
|
||||
if errors:
|
||||
self.__errors__[name] = errors
|
||||
self.__errors__[alias] = errors
|
||||
self.__values__[name] = value
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ from distutils.version import StrictVersion
|
||||
|
||||
__all__ = ['VERSION']
|
||||
|
||||
VERSION = StrictVersion('0.0.7')
|
||||
VERSION = StrictVersion('0.0.8')
|
||||
|
||||
@@ -359,3 +359,29 @@ def test_any_dict():
|
||||
assert Model(v={1: 'foobar'}).values == {'v': {1: 'foobar'}}
|
||||
assert Model(v={123: 456}).values == {'v': {123: 456}}
|
||||
assert Model(v={2: [1, 2, 3]}).values == {'v': {2: [1, 2, 3]}}
|
||||
|
||||
|
||||
def test_infer_alias():
|
||||
class Model(BaseModel):
|
||||
a = 'foobar'
|
||||
|
||||
class Config:
|
||||
fields = {'a': '_a'}
|
||||
|
||||
assert Model(_a='different').a == 'different'
|
||||
|
||||
|
||||
def test_alias_error():
|
||||
class Model(BaseModel):
|
||||
a = 123
|
||||
|
||||
class Config:
|
||||
fields = {'a': '_a'}
|
||||
|
||||
assert Model(_a='123').a == 123
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Model(_a='foo')
|
||||
assert """\
|
||||
1 error validating input
|
||||
_a:
|
||||
invalid literal for int() with base 10: 'foo' (error_type=ValueError track=int)""" == str(exc_info.value)
|
||||
|
||||
@@ -243,3 +243,18 @@ def test_any():
|
||||
|
||||
assert AnyModel().a == 10
|
||||
assert AnyModel(a='foobar').a == 'foobar'
|
||||
|
||||
|
||||
def test_alias():
|
||||
class Model(BaseModel):
|
||||
a = 'foobar'
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
'a': {'alias': '_a'}
|
||||
}
|
||||
|
||||
assert Model().a == 'foobar'
|
||||
assert Model().values == {'a': 'foobar'}
|
||||
assert Model(_a='different').a == 'different'
|
||||
assert Model(_a='different').values == {'a': 'different'}
|
||||
|
||||
@@ -32,3 +32,15 @@ apple:
|
||||
def test_other_setting(env):
|
||||
with pytest.raises(ValidationError):
|
||||
SimpleSettings(apple='a', foobar=42)
|
||||
|
||||
|
||||
def test_env_with_aliass(env):
|
||||
class Settings(BaseSettings):
|
||||
apple: str = ...
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
'apple': 'BOOM'
|
||||
}
|
||||
env.set('BOOM', 'hello')
|
||||
assert Settings().apple == 'hello'
|
||||
|
||||
Reference in New Issue
Block a user