Skip to content

Commit

Permalink
Merge commit '8ccc5708f120f65582cf9238592c6004efffc32d'
Browse files Browse the repository at this point in the history
* commit '8ccc5708f120f65582cf9238592c6004efffc32d': (22 commits)
  remove data.json, fix pydantic#1992 (pydantic#1994)
  Include tests in source distributions (pydantic#1976)
  Fix const validator not running when class validators are present (pydantic#1957)
  Force fields.Undefined to be a singleton objectIn various places of the code, we compare directly to `fields.Undefined`since we assume it to be constant.When new models get created however, the object is deepcopied andis no longer identical with the original object.We therefore add `__copy__` and `__deepcopy__` methods to ensurethat the copied objects are actually the same original object. (pydantic#1981)
  test_config_file_settings_nornir: use less common env. var names (pydantic#1977)
  fix linting
  docs: fix typo (pydantic#1959)
  - fix typo in docs/index.md (pydantic#1921)
  Fix typo in docstring (pydantic#1866)
  add a __call__ stub to PyObject for mypy (pydantic#1849)
  remove strict_optional from sample mypy.ini in doc (pydantic#1830)
  docs: Fix incorrect description of copy method (pydantic#1821)
  feat(tools): add `parse_raw_as` util (pydantic#1813)
  Bump isort from 5.5.4 to 5.6.1 (pydantic#1980)
  feat(dotenv): support home directory relative paths (e.g. ~/.env) (pydantic#1804)
  combined uprev of dependencies (pydantic#1978)
  Bump pytest from 6.0.1 to 6.1.1 (pydantic#1965)
  clarify argument type to parse_file (pydantic#1795)
  Fix pydantic#1770 (pydantic#1771)
  fix(schema): add basic support of Pattern type in schema generation (pydantic#1768)
  ...

# Conflicts:
#	docs/index.md
#	docs/mypy_plugin.md
  • Loading branch information
inabayuta committed Oct 12, 2020
2 parents 40c1293 + 8ccc570 commit 5d961bf
Show file tree
Hide file tree
Showing 40 changed files with 372 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -224,7 +224,7 @@ jobs:
CIBW_SKIP: '*-win32'
CIBW_PLATFORM: '${{ matrix.platform || matrix.os }}'
CIBW_BEFORE_BUILD: 'pip install -U cython'
CIBW_TEST_REQUIRES: 'pytest==5.4.1'
CIBW_TEST_REQUIRES: 'pytest==6.1.1 pytest-mock==3.3.1'
CIBW_TEST_COMMAND: 'pytest {project}/tests'
CIBW_MANYLINUX_X86_64_IMAGE: 'manylinux2014'
CIBW_MANYLINUX_I686_IMAGE: 'manylinux2014'
Expand Down
3 changes: 3 additions & 0 deletions MANIFEST.in
@@ -1,3 +1,6 @@
include LICENSE
include README.md
include HISTORY.md
graft tests
global-exclude __pycache__
global-exclude *.py[cod]
1 change: 1 addition & 0 deletions changes/1352-brianmaissy.md
@@ -0,0 +1 @@
Add a `__call__` stub to `PyObject` so that mypy will know that it is callable
1 change: 1 addition & 0 deletions changes/1736-PrettyWood.md
@@ -0,0 +1 @@
Fix behaviour with forward refs and optional fields in nested models
1 change: 1 addition & 0 deletions changes/1767-PrettyWood.md
@@ -0,0 +1 @@
add basic support of Pattern type in schema generation
1 change: 1 addition & 0 deletions changes/1770-selimb.md
@@ -0,0 +1 @@
Fix false positive from mypy plugin when a class nested within a `BaseModel` is named `Model`.
2 changes: 2 additions & 0 deletions changes/1794-mdavis-xyz.md
@@ -0,0 +1,2 @@
Clarify documentation for `parse_file` to show that the argument
should be a file *path* not a file-like object.
1 change: 1 addition & 0 deletions changes/1803-PrettyWood.md
@@ -0,0 +1 @@
Support home directory relative paths for `dotenv` files (e.g. `~/.env`).
1 change: 1 addition & 0 deletions changes/1812-PrettyWood.md
@@ -0,0 +1 @@
add `parse_raw_as` utility function
1 change: 1 addition & 0 deletions changes/1821-KimMachineGun.md
@@ -0,0 +1 @@
Fix typo in the anchor of exporting_models.md#modelcopy and incorrect description.
1 change: 1 addition & 0 deletions changes/1957-hmvp.md
@@ -0,0 +1 @@
Fix const validators not running when custom validators are present
1 change: 1 addition & 0 deletions changes/1976-sbraz.md
@@ -0,0 +1 @@
Include tests in source distributions.
1 change: 1 addition & 0 deletions changes/1981-daviskirk.md
@@ -0,0 +1 @@
Force `fields.Undefined` to be a singleton object, fixing inherited generic model schemas
7 changes: 7 additions & 0 deletions docs/build/exec_examples.py
Expand Up @@ -207,6 +207,13 @@ def error(desc: str):
else:
lines = lines[ignore_above + 1 :]

try:
ignore_below = lines.index('# ignore-below')
except ValueError:
pass
else:
lines = lines[:ignore_below]

lines = '\n'.join(lines).split('\n')
if any(len(l) > MAX_LINE_LENGTH for l in lines):
error(f'lines longer than {MAX_LINE_LENGTH} characters')
Expand Down
7 changes: 7 additions & 0 deletions docs/build/schema_mapping.py
Expand Up @@ -145,6 +145,13 @@
'JSON Schema Validation',
''
],
[
'Pattern',
'string',
{'format': 'regex'},
'JSON Schema Validation',
''
],
[
'bytes',
'string',
Expand Down
10 changes: 10 additions & 0 deletions docs/examples/models_parse.py
@@ -1,5 +1,7 @@
import pickle
from datetime import datetime
from pathlib import Path

from pydantic import BaseModel, ValidationError


Expand Down Expand Up @@ -30,3 +32,11 @@ class User(BaseModel):
pickle_data, content_type='application/pickle', allow_pickle=True
)
print(m)

path = Path('data.json')
path.write_text('{"id": 123, "name": "James"}')
m = User.parse_file(path)
print(m)
# ignore-below
if path.exists():
path.unlink()
4 changes: 4 additions & 0 deletions docs/index.md
Expand Up @@ -55,12 +55,16 @@ What's going on here:
<!--
* `signup_ts` is a datetime field which is not required (and takes the value ``None`` if it's not supplied).
*pydantic* will process either a unix timestamp int (e.g. `1496498400`) or a string representing the date & time.
<<<<<<< HEAD
-->
* `signup_ts` は datetime フィールドです (指定されていない場合は `None` 値を取ります)。
*pydantic* は Unix タイムスタンプ (例: `1496498400`) または日付と時刻を表す文字列のいずれかを処理します。

<!--
* `friends` uses python's typing system, and requires a list of inputs. As with `id`, integer-like objects
=======
* `friends` uses python's typing system, and requires a list of integers. As with `id`, integer-like objects
>>>>>>> 8ccc5708f120f65582cf9238592c6004efffc32d
will be converted to integers.
-->
* `friends` は Python の typing を使用しており、入力のリストが必要です。`id` のように、
Expand Down
5 changes: 4 additions & 1 deletion docs/mypy_plugin.md
Expand Up @@ -151,12 +151,16 @@ There are other benefits too! See below for more details.
-->
* `@pydantic.dataclasess.dataclass` デコレータは `Config` サブクラスと同じ意味を持つ `config` キーワード引数を受け入れます。

<<<<<<< HEAD
<!--
### Optional Capabilites:
-->
### オプション機能

<!--
=======
### Optional Capabilities:
>>>>>>> 8ccc5708f120f65582cf9238592c6004efffc32d
#### Prevent the use of required dynamic aliases
-->
#### 必須の動的エイリアスの使用を防止
Expand Down Expand Up @@ -294,7 +298,6 @@ A `mypy.ini` file with all plugin strictness flags enabled (and some other mypy
plugins = pydantic.mypy

follow_imports = silent
strict_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
Expand Down
6 changes: 3 additions & 3 deletions docs/requirements.txt
@@ -1,10 +1,10 @@
ansi2html==1.5.2
mkdocs==1.1.2
markdown==3.2.2
markdown==3.3
mkdocs-exclude==1.0.2
mkdocs-material==5.5.12
mkdocs-material==6.0.2
markdown-include==0.6.0
pygments==2.6.1
pygments==2.7.1
sqlalchemy # pyup: ignore
orjson # pyup: ignore
ujson # pyup: ignore
10 changes: 5 additions & 5 deletions docs/usage/models.md
Expand Up @@ -71,7 +71,7 @@ Models possess the following methods and attributes:
cf. [exporting models](exporting_models.md#modeljson)

`copy()`
: returns a deep copy of the model; cf. [exporting models](exporting_models.md#modeldcopy)
: returns a copy (by default, shallow copy) of the model; cf. [exporting models](exporting_models.md#modelcopy)

`parse_obj()`
: a utility for loading any object into a model with error handling if the object is not a dictionary;
Expand All @@ -81,7 +81,7 @@ Models possess the following methods and attributes:
: a utility for loading strings of numerous formats; cf. [helper functions](#helper-functions)

`parse_file()`
: like `parse_raw()` but for files; cf. [helper function](#helper-functions)
: like `parse_raw()` but for file paths; cf. [helper function](#helper-functions)

`from_orm()`
: loads data into a model from an arbitrary class; cf. [ORM mode](#orm-mode-aka-arbitrary-class-instances)
Expand Down Expand Up @@ -238,7 +238,7 @@ _(This script is complete, it should run "as is")_
rather than keyword arguments. If the object passed is not a dict a `ValidationError` will be raised.
* **`parse_raw`**: this takes a *str* or *bytes* and parses it as *json*, then passes the result to `parse_obj`.
Parsing *pickle* data is also supported by setting the `content_type` argument appropriately.
* **`parse_file`**: this reads a file and passes the contents to `parse_raw`. If `content_type` is omitted,
* **`parse_file`**: this takes in a file path, reads the file and passes the contents to `parse_raw`. If `content_type` is omitted,
it is inferred from the file's extension.

```py
Expand Down Expand Up @@ -533,8 +533,8 @@ _(This script is complete, it should run "as is")_

This function is capable of parsing data into any of the types pydantic can handle as fields of a `BaseModel`.

Pydantic also includes a similar standalone function called `parse_file_as`,
which is analogous to `BaseModel.parse_file`.
Pydantic also includes two similar standalone functions called `parse_file_as` and `parse_raw_as`,
which are analogous to `BaseModel.parse_file` and `BaseModel.parse_raw`.

## Data Conversion

Expand Down
2 changes: 1 addition & 1 deletion docs/usage/validators.md
Expand Up @@ -53,7 +53,7 @@ A few more things to note:
## Subclass Validators and `each_item`

If using a validator with a subclass that references a `List` type field on a parent class, using `each_item=True` will
cause the validator not to run; instead, the list must be iterated over programatically.
cause the validator not to run; instead, the list must be iterated over programmatically.

```py
{!.tmp_examples/validators_subclass_each_item.py!}
Expand Down
1 change: 1 addition & 0 deletions pydantic/__init__.py
Expand Up @@ -56,6 +56,7 @@
# tools
'parse_file_as',
'parse_obj_as',
'parse_raw_as',
# types
'NoneStr',
'NoneBytes',
Expand Down
2 changes: 1 addition & 1 deletion pydantic/env_settings.py
Expand Up @@ -58,7 +58,7 @@ def _build_environ(
env_file = _env_file if _env_file != env_file_sentinel else self.__config__.env_file
env_file_encoding = _env_file_encoding if _env_file_encoding is not None else self.__config__.env_file_encoding
if env_file is not None:
env_path = Path(env_file)
env_path = Path(env_file).expanduser()
if env_path.is_file():
env_vars = {
**read_env_file(
Expand Down
34 changes: 17 additions & 17 deletions pydantic/fields.py
@@ -1,6 +1,5 @@
import warnings
from collections.abc import Iterable as CollectionsIterable
from copy import deepcopy
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -36,16 +35,24 @@
is_new_type,
new_type_supertype,
)
from .utils import PyObjectStr, Representation, lenient_issubclass, sequence_like
from .utils import PyObjectStr, Representation, lenient_issubclass, sequence_like, smart_deepcopy
from .validators import constant_validator, dict_validator, find_validators, validate_json

Required: Any = Ellipsis

T = TypeVar('T')


class UndefinedType:
def __repr__(self) -> str:
return 'PydanticUndefined'

def __copy__(self: T) -> T:
return self

def __deepcopy__(self: T, _: Any) -> T:
return self


Undefined = UndefinedType()

Expand Down Expand Up @@ -129,7 +136,7 @@ def Field(
**extra: Any,
) -> Any:
"""
Used to provide extra information about a field, either for the model schema or complex valiation. Some arguments
Used to provide extra information about a field, either for the model schema or complex validation. Some arguments
apply only to number fields (``int``, ``float``, ``Decimal``) and some apply only to ``str``.
:param default: since this is replacing the field’s default, its first argument is used
Expand Down Expand Up @@ -271,14 +278,7 @@ def __init__(
self.prepare()

def get_default(self) -> Any:
if self.default_factory is not None:
value = self.default_factory()
elif self.default is None:
# deepcopy is quite slow on None
value = None
else:
value = deepcopy(self.default)
return value
return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory()

@classmethod
def infer(
Expand Down Expand Up @@ -342,6 +342,11 @@ def prepare(self) -> None:
"""

self._set_default_and_type()
if self.type_.__class__ == ForwardRef:
# self.type_ is currently a ForwardRef and there's nothing we can do now,
# user will need to call model.update_forward_refs()
return

self._type_analysis()
if self.required is Undefined:
self.required = True
Expand Down Expand Up @@ -374,11 +379,6 @@ def _set_default_and_type(self) -> None:
if self.type_ is None:
raise errors_.ConfigError(f'unable to infer type for attribute "{self.name}"')

if self.type_.__class__ == ForwardRef:
# self.type_ is currently a ForwardRef and there's nothing we can do now,
# user will need to call model.update_forward_refs()
return

if self.required is False and default_value is None:
self.allow_none = True

Expand Down Expand Up @@ -539,7 +539,7 @@ def populate_validators(self) -> None:

if class_validators_:
self.pre_validators += prep_validators(v.func for v in class_validators_ if not v.each_item and v.pre)
self.post_validators = prep_validators(v.func for v in class_validators_ if not v.each_item and not v.pre)
self.post_validators += prep_validators(v.func for v in class_validators_ if not v.each_item and not v.pre)

if self.parse_json:
self.pre_validators.append(make_generic_validator(validate_json))
Expand Down
6 changes: 4 additions & 2 deletions pydantic/main.py
Expand Up @@ -42,6 +42,7 @@
generate_model_signature,
lenient_issubclass,
sequence_like,
smart_deepcopy,
unique_list,
validate_field_name,
)
Expand Down Expand Up @@ -219,7 +220,7 @@ def __new__(mcs, name, bases, namespace, **kwargs): # noqa C901
pre_root_validators, post_root_validators = [], []
for base in reversed(bases):
if _is_base_model_class_defined and issubclass(base, BaseModel) and base != BaseModel:
fields.update(deepcopy(base.__fields__))
fields.update(smart_deepcopy(base.__fields__))
config = inherit_config(base.__config__, config)
validators = inherit_validators(base.__validators__, validators)
pre_root_validators += base.__pre_root_validators__
Expand Down Expand Up @@ -527,7 +528,7 @@ def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, **valu
Default values are respected, but no other validation is performed.
"""
m = cls.__new__(cls)
object.__setattr__(m, '__dict__', {**deepcopy(cls.__field_defaults__), **values})
object.__setattr__(m, '__dict__', {**smart_deepcopy(cls.__field_defaults__), **values})
if _fields_set is None:
_fields_set = set(values.keys())
object.__setattr__(m, '__fields_set__', _fields_set)
Expand Down Expand Up @@ -558,6 +559,7 @@ def copy(
)

if deep:
# chances of having empty dict here are quite low for using smart_deepcopy
v = deepcopy(v)

cls = self.__class__
Expand Down
2 changes: 1 addition & 1 deletion pydantic/mypy.py
Expand Up @@ -328,7 +328,7 @@ def add_construct_method(self, fields: List['PydanticModelField']) -> None:
construct_arguments = [fields_set_argument] + construct_arguments

obj_type = ctx.api.named_type('__builtins__.object')
self_tvar_name = 'Model'
self_tvar_name = '_PydanticBaseModel' # Make sure it does not conflict with other names in the class
tvar_fullname = ctx.cls.fullname + '.' + self_tvar_name
tvd = TypeVarDef(self_tvar_name, tvar_fullname, -1, [], obj_type)
self_tvar_expr = TypeVarExpr(self_tvar_name, tvar_fullname, [], obj_type)
Expand Down
5 changes: 4 additions & 1 deletion pydantic/schema.py
Expand Up @@ -14,6 +14,7 @@
Iterable,
List,
Optional,
Pattern,
Sequence,
Set,
Tuple,
Expand Down Expand Up @@ -618,6 +619,7 @@ def field_singleton_sub_fields_schema(
(IPv6Interface, {'type': 'string', 'format': 'ipv6interface'}),
(IPv4Address, {'type': 'string', 'format': 'ipv4'}),
(IPv6Address, {'type': 'string', 'format': 'ipv6'}),
(Pattern, {'type': 'string', 'format': 'regex'}),
(str, {'type': 'string'}),
(bytes, {'type': 'string', 'format': 'binary'}),
(bool, {'type': 'boolean'}),
Expand All @@ -643,7 +645,8 @@ def add_field_type_to_schema(field_type: Any, schema: Dict[str, Any]) -> None:
and then modifies the given `schema` with the information from that type.
"""
for type_, t_schema in field_class_to_schema:
if issubclass(field_type, type_):
# Fallback for `typing.Pattern` as it is not a valid class
if lenient_issubclass(field_type, type_) or field_type is type_ is Pattern:
schema.update(t_schema)
break

Expand Down

0 comments on commit 5d961bf

Please sign in to comment.