Skip to content

Commit 6f46a5a

Browse files
authored
drop python3.6 support (pydantic#3605)
* drop python3.6 support * revert small change * fix 3.7 failures * more cases and cleanup * add change description
1 parent fbf8002 commit 6f46a5a

30 files changed

+85
-263
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
strategy:
6666
fail-fast: false
6767
matrix:
68-
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
68+
python-version: ['3.7', '3.8', '3.9', '3.10']
6969
env:
7070
PYTHON: ${{ matrix.python-version }}
7171
OS: ubuntu
@@ -131,7 +131,7 @@ jobs:
131131
fail-fast: false
132132
matrix:
133133
os: [macos, windows]
134-
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
134+
python-version: ['3.7', '3.8', '3.9', '3.10']
135135
env:
136136
PYTHON: ${{ matrix.python-version }}
137137
OS: ${{ matrix.os }}
@@ -283,7 +283,7 @@ jobs:
283283
fail-fast: false
284284
matrix:
285285
os: [ubuntu , macos , windows]
286-
python-version: ['6', '7', '8', '9', '10']
286+
python-version: ['7', '8', '9', '10']
287287
include:
288288
- os: ubuntu
289289
platform: linux

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
[![versions](https://img.shields.io/pypi/pyversions/pydantic.svg)](https://github.com/samuelcolvin/pydantic)
99
[![license](https://img.shields.io/github/license/samuelcolvin/pydantic.svg)](https://github.com/samuelcolvin/pydantic/blob/master/LICENSE)
1010

11-
Data validation and settings management using Python type hinting.
11+
Data validation and settings management using Python type hints.
1212

1313
Fast and extensible, *pydantic* plays nicely with your linters/IDE/brain.
14-
Define how data should be in pure, canonical Python 3.6+; validate it with *pydantic*.
14+
Define how data should be in pure, canonical Python 3.7+; validate it with *pydantic*.
1515

1616
## Help
1717

changes/3605-samuelcolvin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Drop support for python3.6, associated cleanup

docs/contributing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ To make contributing as easy and fast as possible, you'll want to run tests and
3333
*pydantic* has few dependencies, doesn't require compiling and tests don't need access to databases, etc.
3434
Because of this, setting up and running the tests should be very simple.
3535

36-
You'll need to have a version between **python 3.6 and 3.10**, **virtualenv**, **git**, and **make** installed.
36+
You'll need to have a version between **python 3.7 and 3.10**, **virtualenv**, **git**, and **make** installed.
3737

3838
```bash
3939
# 1. clone your fork and cd into the repo directory
@@ -44,7 +44,7 @@ cd pydantic
4444
virtualenv -p `which python3.8` env
4545
source env/bin/activate
4646
# Building docs requires 3.8. If you don't need to build docs you can use
47-
# whichever version; 3.6 will work too.
47+
# whichever version; 3.7 will work too.
4848

4949
# 3. Install pydantic, dependencies, test dependencies and doc dependencies
5050
make install

docs/install.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ Installation is as simple as:
44
pip install pydantic
55
```
66

7-
*pydantic* has no required dependencies except python 3.6, 3.7, 3.8, 3.9 or 3.10,
8-
[`typing-extensions`](https://pypi.org/project/typing-extensions/), and the
9-
[`dataclasses`](https://pypi.org/project/dataclasses/) backport package for python 3.6.
10-
If you've got python 3.6+ and `pip` installed, you're good to go.
7+
*pydantic* has no required dependencies except python 3.7, 3.8, 3.9 or 3.10 and
8+
[`typing-extensions`](https://pypi.org/project/typing-extensions/).
9+
If you've got python 3.7+ and `pip` installed, you're good to go.
1110

1211
Pydantic is also available on [conda](https://www.anaconda.com) under the [conda-forge](https://conda-forge.org)
1312
channel:

docs/usage/dataclasses.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
If you don't want to use _pydantic_'s `BaseModel` you can instead get the same data validation on standard
22
[dataclasses](https://docs.python.org/3/library/dataclasses.html) (introduced in python 3.7).
33

4-
Dataclasses work in python 3.6 using the [dataclasses backport package](https://github.com/ericvsmith/dataclasses).
5-
64
```py
75
{!.tmp_examples/dataclasses_main.py!}
86
```

docs/usage/models.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,6 @@ For example, in the example above, if `_fields_set` was not provided,
296296

297297
Pydantic supports the creation of generic models to make it easier to reuse a common model structure.
298298

299-
!!! warning
300-
Generic models are only supported with python `>=3.7`, this is because of numerous subtle changes in how
301-
generics are implemented between python 3.6 and python 3.7.
302-
303299
In order to declare a generic model, you perform the following steps:
304300

305301
* Declare one or more `typing.TypeVar` instances to use to parameterize your model.

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
site_name: pydantic
2-
site_description: Data validation and settings management using python 3.6 type hinting
2+
site_description: Data validation and settings management using python type hints
33
strict: true
44
site_url: https://pydantic-docs.helpmanual.io/
55

pydantic/config.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from enum import Enum
3-
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type, Union
3+
from typing import TYPE_CHECKING, Any, Callable, Dict, ForwardRef, Optional, Tuple, Type, Union
44

55
from .typing import AnyCallable
66
from .utils import GetterDict
@@ -59,8 +59,7 @@ class BaseConfig:
5959
schema_extra: Union[Dict[str, Any], 'SchemaExtraCallable'] = {}
6060
json_loads: Callable[[str], Any] = json.loads
6161
json_dumps: Callable[..., str] = json.dumps
62-
# key type should include ForwardRef, but that breaks with python3.6
63-
json_encoders: Dict[Union[Type[Any], str], AnyCallable] = {}
62+
json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable] = {}
6463
underscore_attrs_are_private: bool = False
6564

6665
# whether inherited models as fields should be reconstructed as base model

pydantic/fields.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,6 @@ def is_complex(self) -> bool:
11351135
def _type_display(self) -> PyObjectStr:
11361136
t = display_as_type(self.type_)
11371137

1138-
# have to do this since display_as_type(self.outer_type_) is different (and wrong) on python 3.6
11391138
if self.shape in MAPPING_LIKE_SHAPES:
11401139
t = f'Mapping[{display_as_type(self.key_field.type_)}, {t}]' # type: ignore
11411140
elif self.shape == SHAPE_TUPLE:

pydantic/json.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
import datetime
2-
import re
3-
import sys
42
from collections import deque
53
from decimal import Decimal
64
from enum import Enum
75
from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network
86
from pathlib import Path
7+
from re import Pattern
98
from types import GeneratorType
109
from typing import Any, Callable, Dict, Type, Union
1110
from uuid import UUID
1211

13-
if sys.version_info >= (3, 7):
14-
Pattern = re.Pattern
15-
else:
16-
# python 3.6
17-
Pattern = re.compile('a').__class__
18-
1912
from .color import Color
2013
from .networks import NameEmail
2114
from .types import SecretBytes, SecretStr

pydantic/typing.py

Lines changed: 27 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import sys
2+
from collections.abc import Callable
23
from os import PathLike
34
from typing import ( # type: ignore
45
TYPE_CHECKING,
56
AbstractSet,
67
Any,
8+
Callable as TypingCallable,
79
ClassVar,
810
Dict,
11+
ForwardRef,
912
Generator,
1013
Iterable,
1114
List,
@@ -36,28 +39,7 @@
3639
TypingGenericAlias = ()
3740

3841

39-
if sys.version_info < (3, 7):
40-
if TYPE_CHECKING:
41-
42-
class ForwardRef:
43-
def __init__(self, arg: Any):
44-
pass
45-
46-
def _eval_type(self, globalns: Any, localns: Any) -> Any:
47-
pass
48-
49-
else:
50-
from typing import _ForwardRef as ForwardRef
51-
else:
52-
from typing import ForwardRef
53-
54-
55-
if sys.version_info < (3, 7):
56-
57-
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
58-
return type_._eval_type(globalns, localns)
59-
60-
elif sys.version_info < (3, 9):
42+
if sys.version_info < (3, 9):
6143

6244
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
6345
return type_._evaluate(globalns, localns)
@@ -72,7 +54,7 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
7254

7355
if sys.version_info < (3, 9):
7456
# Ensure we always get all the whole `Annotated` hint, not just the annotated type.
75-
# For 3.6 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`,
57+
# For 3.7 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`,
7658
# so it already returns the full annotation
7759
get_all_type_hints = get_type_hints
7860

@@ -82,17 +64,8 @@ def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> A
8264
return get_type_hints(obj, globalns, localns, include_extras=True)
8365

8466

85-
if sys.version_info < (3, 7):
86-
from typing import Callable as Callable
87-
88-
AnyCallable = Callable[..., Any]
89-
NoArgAnyCallable = Callable[[], Any]
90-
else:
91-
from collections.abc import Callable as Callable
92-
from typing import Callable as TypingCallable
93-
94-
AnyCallable = TypingCallable[..., Any]
95-
NoArgAnyCallable = TypingCallable[[], Any]
67+
AnyCallable = TypingCallable[..., Any]
68+
NoArgAnyCallable = TypingCallable[[], Any]
9669

9770

9871
# Annotated[...] is implemented by returning an instance of one of these classes, depending on
@@ -104,7 +77,8 @@ def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> A
10477

10578
def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
10679
if type(t).__name__ in AnnotatedTypeNames:
107-
return cast(Type[Any], Annotated) # mypy complains about _SpecialForm in py3.6
80+
# weirdly this is a runtime requirement, as well as for mypy
81+
return cast(Type[Any], Annotated)
10882
return getattr(t, '__origin__', None)
10983

11084
else:
@@ -122,22 +96,7 @@ def get_origin(tp: Type[Any]) -> Optional[Type[Any]]:
12296
return _typing_get_origin(tp) or getattr(tp, '__origin__', None)
12397

12498

125-
if sys.version_info < (3, 7): # noqa: C901 (ignore complexity)
126-
127-
def get_args(t: Type[Any]) -> Tuple[Any, ...]:
128-
"""Simplest get_args compatibility layer possible.
129-
130-
The Python 3.6 typing module does not have `_GenericAlias` so
131-
this won't work for everything. In particular this will not
132-
support the `generics` module (we don't support generic models in
133-
python 3.6).
134-
135-
"""
136-
if type(t).__name__ in AnnotatedTypeNames:
137-
return t.__args__ + t.__metadata__
138-
return getattr(t, '__args__', ())
139-
140-
elif sys.version_info < (3, 8): # noqa: C901
99+
if sys.version_info < (3, 8):
141100
from typing import _GenericAlias
142101

143102
def get_args(t: Type[Any]) -> Tuple[Any, ...]:
@@ -279,8 +238,8 @@ def is_union(tp: Optional[Type[Any]]) -> bool:
279238

280239

281240
if sys.version_info < (3, 8):
282-
# Even though this implementation is slower, we need it for python 3.6/3.7:
283-
# In python 3.6/3.7 "Literal" is not a builtin type and uses a different
241+
# Even though this implementation is slower, we need it for python 3.7:
242+
# In python 3.7 "Literal" is not a builtin type and uses a different
284243
# mechanism.
285244
# for this reason `Literal[None] is Literal[None]` evaluates to `False`,
286245
# breaking the faster implementation used for the other python versions.
@@ -348,10 +307,8 @@ def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Opti
348307
if isinstance(value, str):
349308
if (3, 10) > sys.version_info >= (3, 9, 8) or sys.version_info >= (3, 10, 1):
350309
value = ForwardRef(value, is_argument=False, is_class=True)
351-
elif sys.version_info >= (3, 7):
352-
value = ForwardRef(value, is_argument=False)
353310
else:
354-
value = ForwardRef(value)
311+
value = ForwardRef(value, is_argument=False)
355312
try:
356313
value = _eval_type(value, base_globals, None)
357314
except NameError:
@@ -365,21 +322,12 @@ def is_callable_type(type_: Type[Any]) -> bool:
365322
return type_ is Callable or get_origin(type_) is Callable
366323

367324

368-
if sys.version_info >= (3, 7):
325+
def is_literal_type(type_: Type[Any]) -> bool:
326+
return Literal is not None and get_origin(type_) is Literal
369327

370-
def is_literal_type(type_: Type[Any]) -> bool:
371-
return Literal is not None and get_origin(type_) is Literal
372328

373-
def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
374-
return get_args(type_)
375-
376-
else:
377-
378-
def is_literal_type(type_: Type[Any]) -> bool:
379-
return Literal is not None and hasattr(type_, '__values__') and type_ == Literal[type_.__values__]
380-
381-
def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
382-
return type_.__values__
329+
def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
330+
return get_args(type_)
383331

384332

385333
def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
@@ -435,7 +383,7 @@ def _check_classvar(v: Optional[Type[Any]]) -> bool:
435383
if v is None:
436384
return False
437385

438-
return v.__class__ == ClassVar.__class__ and (sys.version_info < (3, 7) or getattr(v, '_name', None) == 'ClassVar')
386+
return v.__class__ == ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar'
439387

440388

441389
def is_classvar(ann_type: Type[Any]) -> bool:
@@ -461,7 +409,7 @@ def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any)
461409
def update_model_forward_refs(
462410
model: Type[Any],
463411
fields: Iterable['ModelField'],
464-
json_encoders: Dict[Union[Type[Any], str], AnyCallable],
412+
json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable],
465413
localns: 'DictStrAny',
466414
exc_to_suppress: Tuple[Type[BaseException], ...] = (),
467415
) -> None:
@@ -502,17 +450,14 @@ def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]:
502450
Tries to get the class of a Type[T] annotation. Returns True if Type is used
503451
without brackets. Otherwise returns None.
504452
"""
505-
try:
506-
origin = get_origin(type_)
507-
if origin is None: # Python 3.6
508-
origin = type_
509-
if issubclass(origin, Type): # type: ignore
510-
if not get_args(type_) or not isinstance(get_args(type_)[0], type):
511-
return True
512-
return get_args(type_)[0]
513-
except (AttributeError, TypeError):
514-
pass
515-
return None
453+
if get_origin(type_) is None:
454+
return None
455+
456+
args = get_args(type_)
457+
if not args or not isinstance(args[0], type):
458+
return True
459+
else:
460+
return args[0]
516461

517462

518463
def get_sub_types(tp: Any) -> List[Any]:

setup.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def extra(self):
5656
return '\n\n' + '\n'.join(sorted(self.links)) + '\n'
5757

5858

59-
description = 'Data validation and settings management using python 3.6 type hinting'
59+
description = 'Data validation and settings management using python type hints'
6060
THIS_DIR = Path(__file__).resolve().parent
6161
try:
6262
history = (THIS_DIR / 'HISTORY.md').read_text()
@@ -104,7 +104,6 @@ def extra(self):
104104
'Programming Language :: Python',
105105
'Programming Language :: Python :: 3',
106106
'Programming Language :: Python :: 3 :: Only',
107-
'Programming Language :: Python :: 3.6',
108107
'Programming Language :: Python :: 3.7',
109108
'Programming Language :: Python :: 3.8',
110109
'Programming Language :: Python :: 3.9',
@@ -127,10 +126,9 @@ def extra(self):
127126
license='MIT',
128127
packages=['pydantic'],
129128
package_data={'pydantic': ['py.typed']},
130-
python_requires='>=3.6.1',
129+
python_requires='>=3.7',
131130
zip_safe=False, # https://mypy.readthedocs.io/en/latest/installed_packages.html
132131
install_requires=[
133-
'dataclasses>=0.6;python_version<"3.7"',
134132
'typing-extensions>=3.7.4.3'
135133
],
136134
extras_require={

tests/mypy/modules/plugin_fail.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class Blah(BaseModel):
114114
fields_set: Optional[Set[str]] = None
115115

116116

117-
# Need to test generic checking here since generics don't work in 3.6, and plugin-success.py is executed
117+
# (comment to keep line numbers unchanged)
118118
T = TypeVar('T')
119119

120120

0 commit comments

Comments
 (0)