Add to upper function for strings and bytes (#4165)

* feat: add to upper function for strings and bytes

* docs(changes): add message for change

* fix: add constr upper on types

* fix: add constr upper on types

* feat: add examples and doc usage

* test: add test to upper for types

* chore: apply suggestions from code review

Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>

* chore(docs): reorder `anystr_upper` to under `anystr_lower`

* fix(test): adjust parametrizes to constrained bytes upper

* refactor: use pytest parametrize for unify test constrained str upper

* refactor: use pytest parametrize for unify test constrained str lower

* refactor(test): use pytest parametrize for unify test any str upper

* refactor(test): use pytest parametrize for unify test any str lower

* refactor(test): use pytest parametrize for unify test constrained bytes lower

* refactor(test): use pytest parametrize for unify test any str strip whitespace

* refactor(test): change test signatures to improve readability

Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>
This commit is contained in:
Gustavo Satheler
2022-08-08 13:06:00 -03:00
committed by GitHub
parent 65a1381591
commit d90def3c4e
8 changed files with 107 additions and 45 deletions
+1
View File
@@ -0,0 +1 @@
Add `Config.anystr_upper` and `to_upper` kwarg to constr and conbytes.
+2
View File
@@ -22,10 +22,12 @@ from pydantic import (
class Model(BaseModel):
upper_bytes: conbytes(to_upper=True)
lower_bytes: conbytes(to_lower=True)
short_bytes: conbytes(min_length=2, max_length=10)
strip_bytes: conbytes(strip_whitespace=True)
upper_str: constr(to_upper=True)
lower_str: constr(to_lower=True)
short_str: constr(min_length=2, max_length=10)
regex_str: constr(regex=r'^apple (pie|tart|sandwich)$')
+3
View File
@@ -25,6 +25,9 @@ _(This script is complete, it should run "as is")_
**`anystr_strip_whitespace`**
: whether to strip leading and trailing whitespace for str & byte types (default: `False`)
**`anystr_upper`**
: whether to make all characters uppercase for str & byte types (default: `False`)
**`anystr_lower`**
: whether to make all characters lowercase for str & byte types (default: `False`)
+2
View File
@@ -910,6 +910,7 @@ The following arguments are available when using the `condecimal` type function
The following arguments are available when using the `constr` type function
- `strip_whitespace: bool = False`: removes leading and trailing whitespace
- `to_upper: bool = False`: turns all characters to uppercase
- `to_lower: bool = False`: turns all characters to lowercase
- `strict: bool = False`: controls type coercion
- `min_length: int = None`: minimum length of the string
@@ -921,6 +922,7 @@ The following arguments are available when using the `constr` type function
The following arguments are available when using the `conbytes` type function
- `strip_whitespace: bool = False`: removes leading and trailing whitespace
- `to_upper: bool = False`: turns all characters to uppercase
- `to_lower: bool = False`: turns all characters to lowercase
- `min_length: int = None`: minimum length of the byte string
- `max_length: int = None`: maximum length of the byte string
+1
View File
@@ -80,6 +80,7 @@ else:
class BaseConfig:
title: Optional[str] = None
anystr_lower: bool = False
anystr_upper: bool = False
anystr_strip_whitespace: bool = False
min_anystr_length: int = 0
max_anystr_length: Optional[int] = None
+9
View File
@@ -35,6 +35,7 @@ from .validators import (
constr_length_validator,
constr_lower,
constr_strip_whitespace,
constr_upper,
decimal_validator,
float_validator,
frozenset_validator,
@@ -328,6 +329,7 @@ else:
class ConstrainedBytes(bytes):
strip_whitespace = False
to_upper = False
to_lower = False
min_length: OptionalInt = None
max_length: OptionalInt = None
@@ -341,6 +343,7 @@ class ConstrainedBytes(bytes):
def __get_validators__(cls) -> 'CallableGenerator':
yield strict_bytes_validator if cls.strict else bytes_validator
yield constr_strip_whitespace
yield constr_upper
yield constr_lower
yield constr_length_validator
@@ -348,6 +351,7 @@ class ConstrainedBytes(bytes):
def conbytes(
*,
strip_whitespace: bool = False,
to_upper: bool = False,
to_lower: bool = False,
min_length: int = None,
max_length: int = None,
@@ -356,6 +360,7 @@ def conbytes(
# use kwargs then define conf in a dict to aid with IDE type hinting
namespace = dict(
strip_whitespace=strip_whitespace,
to_upper=to_upper,
to_lower=to_lower,
min_length=min_length,
max_length=max_length,
@@ -377,6 +382,7 @@ else:
class ConstrainedStr(str):
strip_whitespace = False
to_upper = False
to_lower = False
min_length: OptionalInt = None
max_length: OptionalInt = None
@@ -397,6 +403,7 @@ class ConstrainedStr(str):
def __get_validators__(cls) -> 'CallableGenerator':
yield strict_str_validator if cls.strict else str_validator
yield constr_strip_whitespace
yield constr_upper
yield constr_lower
yield constr_length_validator
yield cls.validate
@@ -416,6 +423,7 @@ class ConstrainedStr(str):
def constr(
*,
strip_whitespace: bool = False,
to_upper: bool = False,
to_lower: bool = False,
strict: bool = False,
min_length: int = None,
@@ -426,6 +434,7 @@ def constr(
# use kwargs then define conf in a dict to aid with IDE type hinting
namespace = dict(
strip_whitespace=strip_whitespace,
to_upper=to_upper,
to_lower=to_lower,
strict=strict,
min_length=min_length,
+14
View File
@@ -206,6 +206,10 @@ def anystr_strip_whitespace(v: 'StrBytes') -> 'StrBytes':
return v.strip()
def anystr_upper(v: 'StrBytes') -> 'StrBytes':
return v.upper()
def anystr_lower(v: 'StrBytes') -> 'StrBytes':
return v.lower()
@@ -488,6 +492,14 @@ def constr_strip_whitespace(v: 'StrBytes', field: 'ModelField', config: 'BaseCon
return v
def constr_upper(v: 'StrBytes', field: 'ModelField', config: 'BaseConfig') -> 'StrBytes':
upper = field.type_.to_upper or config.anystr_upper
if upper:
v = v.upper()
return v
def constr_lower(v: 'StrBytes', field: 'ModelField', config: 'BaseConfig') -> 'StrBytes':
lower = field.type_.to_lower or config.anystr_lower
if lower:
@@ -614,6 +626,7 @@ _VALIDATORS: List[Tuple[Type[Any], List[Any]]] = [
[
str_validator,
IfConfig(anystr_strip_whitespace, 'anystr_strip_whitespace'),
IfConfig(anystr_upper, 'anystr_upper'),
IfConfig(anystr_lower, 'anystr_lower'),
IfConfig(anystr_length_validator, 'min_anystr_length', 'max_anystr_length'),
],
@@ -623,6 +636,7 @@ _VALIDATORS: List[Tuple[Type[Any], List[Any]]] = [
[
bytes_validator,
IfConfig(anystr_strip_whitespace, 'anystr_strip_whitespace'),
IfConfig(anystr_upper, 'anystr_upper'),
IfConfig(anystr_lower, 'anystr_lower'),
IfConfig(anystr_length_validator, 'min_anystr_length', 'max_anystr_length'),
],
+75 -45
View File
@@ -113,20 +113,34 @@ def test_constrained_bytes_too_long():
]
def test_constrained_bytes_lower_enabled():
@pytest.mark.parametrize(
'to_upper, value, result',
[
(True, b'abcd', b'ABCD'),
(False, b'aBcD', b'aBcD'),
],
)
def test_constrained_bytes_upper(to_upper, value, result):
class Model(BaseModel):
v: conbytes(to_lower=True)
v: conbytes(to_upper=to_upper)
m = Model(v=b'ABCD')
assert m.v == b'abcd'
m = Model(v=value)
assert m.v == result
def test_constrained_bytes_lower_disabled():
@pytest.mark.parametrize(
'to_lower, value, result',
[
(True, b'ABCD', b'abcd'),
(False, b'ABCD', b'ABCD'),
],
)
def test_constrained_bytes_lower(to_lower, value, result):
class Model(BaseModel):
v: conbytes(to_lower=False)
v: conbytes(to_lower=to_lower)
m = Model(v=b'ABCD')
assert m.v == b'ABCD'
m = Model(v=value)
assert m.v == result
def test_constrained_bytes_strict_true():
@@ -699,20 +713,34 @@ def test_constrained_str_too_long():
]
def test_constrained_str_lower_enabled():
@pytest.mark.parametrize(
'to_upper, value, result',
[
(True, 'abcd', 'ABCD'),
(False, 'aBcD', 'aBcD'),
],
)
def test_constrained_str_upper(to_upper, value, result):
class Model(BaseModel):
v: constr(to_lower=True)
v: constr(to_upper=to_upper)
m = Model(v='ABCD')
assert m.v == 'abcd'
m = Model(v=value)
assert m.v == result
def test_constrained_str_lower_disabled():
@pytest.mark.parametrize(
'to_lower, value, result',
[
(True, 'ABCD', 'abcd'),
(False, 'ABCD', 'ABCD'),
],
)
def test_constrained_str_lower(to_lower, value, result):
class Model(BaseModel):
v: constr(to_lower=False)
v: constr(to_lower=to_lower)
m = Model(v='ABCD')
assert m.v == 'ABCD'
m = Model(v=value)
assert m.v == result
def test_constrained_str_max_length_0():
@@ -1784,58 +1812,60 @@ def test_uuid_validation():
]
def test_anystr_strip_whitespace_enabled():
@pytest.mark.parametrize(
'enabled, str_check, bytes_check, result_str_check, result_bytes_check',
[
(True, ' 123 ', b' 456 ', '123', b'456'),
(False, ' 123 ', b' 456 ', ' 123 ', b' 456 '),
],
)
def test_anystr_strip_whitespace(enabled, str_check, bytes_check, result_str_check, result_bytes_check):
class Model(BaseModel):
str_check: str
bytes_check: bytes
class Config:
anystr_strip_whitespace = True
anystr_strip_whitespace = enabled
m = Model(str_check=' 123 ', bytes_check=b' 456 ')
assert m.str_check == '123'
assert m.bytes_check == b'456'
m = Model(str_check=str_check, bytes_check=bytes_check)
assert m.str_check == result_str_check
assert m.bytes_check == result_bytes_check
def test_anystr_strip_whitespace_disabled():
@pytest.mark.parametrize(
'enabled, str_check, bytes_check, result_str_check, result_bytes_check',
[(True, 'ABCDefG', b'abCD1Fg', 'ABCDEFG', b'ABCD1FG'), (False, 'ABCDefG', b'abCD1Fg', 'ABCDefG', b'abCD1Fg')],
)
def test_anystr_upper(enabled, str_check, bytes_check, result_str_check, result_bytes_check):
class Model(BaseModel):
str_check: str
bytes_check: bytes
class Config:
anystr_strip_whitespace = False
anystr_upper = enabled
m = Model(str_check=' 123 ', bytes_check=b' 456 ')
assert m.str_check == ' 123 '
assert m.bytes_check == b' 456 '
m = Model(str_check=str_check, bytes_check=bytes_check)
assert m.str_check == result_str_check
assert m.bytes_check == result_bytes_check
def test_anystr_lower_enabled():
@pytest.mark.parametrize(
'enabled, str_check, bytes_check, result_str_check, result_bytes_check',
[(True, 'ABCDefG', b'abCD1Fg', 'abcdefg', b'abcd1fg'), (False, 'ABCDefG', b'abCD1Fg', 'ABCDefG', b'abCD1Fg')],
)
def test_anystr_lower(enabled, str_check, bytes_check, result_str_check, result_bytes_check):
class Model(BaseModel):
str_check: str
bytes_check: bytes
class Config:
anystr_lower = True
anystr_lower = enabled
m = Model(str_check='ABCDefG', bytes_check=b'abCD1Fg')
m = Model(str_check=str_check, bytes_check=bytes_check)
assert m.str_check == 'abcdefg'
assert m.bytes_check == b'abcd1fg'
def test_anystr_lower_disabled():
class Model(BaseModel):
str_check: str
bytes_check: bytes
class Config:
anystr_lower = False
m = Model(str_check='ABCDefG', bytes_check=b'abCD1Fg')
assert m.str_check == 'ABCDefG'
assert m.bytes_check == b'abCD1Fg'
assert m.str_check == result_str_check
assert m.bytes_check == result_bytes_check
@pytest.mark.parametrize(