diff --git a/Makefile b/Makefile index e4eed75b7a4..5b3dc1e4d17 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .DEFAULT_GOAL := all -isort = isort pydantic tests -black = black -S -l 120 --target-version py38 pydantic tests +sources = pydantic tests docs/build +isort = isort $(sources) +black = black -S -l 120 --target-version py38 $(sources) .PHONY: install-linting install-linting: @@ -35,13 +36,13 @@ build: .PHONY: format format: - pyupgrade --py37-plus --exit-zero-even-if-changed `find pydantic tests -name "*.py" -type f` + pyupgrade --py37-plus --exit-zero-even-if-changed `find $(sources) -name "*.py" -type f` $(isort) $(black) .PHONY: lint lint: - flake8 pydantic/ tests/ + flake8 $(sources) $(isort) --check-only --df $(black) --check --diff @@ -53,7 +54,7 @@ check-dist: .PHONY: mypy mypy: - mypy pydantic + mypy pydantic docs/build .PHONY: pyupgrade pyupgrade: diff --git a/docs/build/exec_examples.py b/docs/build/exec_examples.py index c6eab0b3209..34a4a4f7f1b 100755 --- a/docs/build/exec_examples.py +++ b/docs/build/exec_examples.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from __future__ import annotations import importlib -import inspect import json import os import re @@ -21,9 +20,11 @@ DOCS_DIR = (THIS_DIR / '..').resolve() EXAMPLES_DIR = DOCS_DIR / 'examples' TMP_EXAMPLES_DIR = DOCS_DIR / '.tmp_examples' -UPGRADED_TMP_EXAMPLES_DIR = TMP_EXAMPLES_DIR / "upgraded" +UPGRADED_TMP_EXAMPLES_DIR = TMP_EXAMPLES_DIR / 'upgraded' -MAX_LINE_LENGTH = int(re.search(r'max_line_length = (\d+)', (EXAMPLES_DIR / '.editorconfig').read_text()).group(1)) +MAX_LINE_LENGTH = int( + re.search(r'max_line_length = (\d+)', (EXAMPLES_DIR / '.editorconfig').read_text()).group(1) # type: ignore +) LONG_LINE = 50 LOWEST_VERSION = (3, 7) HIGHEST_VERSION = (3, 10) @@ -32,7 +33,7 @@ Version = tuple[int, int] PYTHON_CODE_MD_TMPL = """ -=== "Python {version} and above" +=== "Python {version} and above" ```py {code} @@ -71,10 +72,9 @@ def __init__(self, file: Path) -> None: self.file = file self.statements: list[tuple[int, str]] = [] - def __call__(self, *args: Any, sep: str = ' ', **kwargs) -> None: - frame = inspect.currentframe().f_back.f_back.f_back - if sys.version_info >= (3, 8): - frame = frame.f_back + def __call__(self, *args: Any, sep: str = ' ', **kwargs: Any) -> None: + frame = sys._getframe(4) if sys.version_info >= (3, 8) else sys._getframe(3) + if not self.file.samefile(frame.f_code.co_filename): # happens when index_error.py imports index_main.py return @@ -185,7 +185,7 @@ def exec_file(file: Path, file_text: str, error: Error) -> tuple[list[str], str mp = MockPrint(file) mod = None - with patch.object(Path, "read_text", MockPath.read_text), patch('builtins.print') as patch_print: + with patch.object(Path, 'read_text', MockPath.read_text), patch('builtins.print') as patch_print: if print_intercept: patch_print.side_effect = mp try: @@ -230,14 +230,14 @@ def filter_lines(lines: list[str], error: Any) -> tuple[list[str], bool]: lines = lines[:ignore_below] lines = '\n'.join(lines).split('\n') - if any(len(l) > MAX_LINE_LENGTH for l in lines): + if any(len(line) > MAX_LINE_LENGTH for line in lines): error(f'lines longer than {MAX_LINE_LENGTH} characters') return lines, ignored_above def upgrade_code(content: str, min_version: Version = HIGHEST_VERSION) -> str: - import pyupgrade._main - import autoflake + import pyupgrade._main # type: ignore + import autoflake # type: ignore upgraded = pyupgrade._main._fix_plugins( content, @@ -261,7 +261,7 @@ def ensure_used(file: Path, all_md: str, error: Error) -> None: error( f'incorrect usage, change filename to {md_name!r} in docs.' "make sure you don't specify ```py code blocks around examples," - "they are automatically generated now." + 'they are automatically generated now.' ) else: error( @@ -282,20 +282,20 @@ def check_style(file_text: str, error: Error) -> None: def populate_upgraded_versions(file: Path, file_text: str, lowest_version: Version) -> list[tuple[Path, str, Version]]: versions = [] major, minor = lowest_version - assert major == HIGHEST_VERSION[0], "Wow, Python 4 is out? Congrats!" + assert major == HIGHEST_VERSION[0], 'Wow, Python 4 is out? Congrats!' upgraded_file_text = file_text while minor < HIGHEST_VERSION[1]: minor += 1 new_file_text = upgrade_code(file_text, min_version=(major, minor)) if upgraded_file_text != new_file_text: upgraded_file_text = new_file_text - new_file = UPGRADED_TMP_EXAMPLES_DIR / (file.stem + f"_{major}_{minor}" + file.suffix) + new_file = UPGRADED_TMP_EXAMPLES_DIR / (file.stem + f'_{major}_{minor}' + file.suffix) new_file.write_text(upgraded_file_text) versions.append((new_file, upgraded_file_text, (major, minor))) return versions -def exec_examples() -> int: +def exec_examples() -> int: # noqa: C901 (I really don't want to decompose it any further) errors = [] all_md = all_md_contents() new_files = {} @@ -324,7 +324,7 @@ def exec_examples() -> int: def error(*desc: str) -> None: errors.append((file, desc)) previous_frame = sys._getframe(1) - filename = Path(previous_frame.f_globals["__file__"]).relative_to(Path.cwd()) + filename = Path(previous_frame.f_globals['__file__']).relative_to(Path.cwd()) location = f'{filename}:{previous_frame.f_lineno}' sys.stderr.write(f'{location}: error in {file.relative_to(Path.cwd())}:\n{" ".join(desc)}\n') @@ -368,7 +368,7 @@ def error(*desc: str) -> None: final_content.append( PYTHON_CODE_MD_TMPL.format( version='.'.join(map(str, lowest_version)), - code=textwrap.indent('\n'.join(lines), " "), + code=textwrap.indent('\n'.join(lines), ' '), ) ) @@ -378,9 +378,10 @@ def error(*desc: str) -> None: final_content.append(f'_(This script requires {requirements})_') else: error( - "script may not run as is, but requirements were not specified.", - "specify `# requires: ` in the end of the script", + 'script may not run as is, but requirements were not specified.', + 'specify `# requires: ` in the end of the script', ) + if len(json_outputs) > 1: error('json output should not differ between versions') @@ -389,7 +390,7 @@ def error(*desc: str) -> None: if json_output: final_content.append(JSON_OUTPUT_MD_TMPL.format(output=json_output)) - new_files[markdown_name] = "\n".join(final_content) + new_files[markdown_name] = '\n'.join(final_content) if errors: print(f'\n{len(errors)} errors, not writing files\n') @@ -403,6 +404,7 @@ def error(*desc: str) -> None: for file_name, content in new_files.items(): (TMP_EXAMPLES_DIR / file_name).write_text(content, 'utf-8') gen_ansi_output() + return 0 diff --git a/docs/build/main.py b/docs/build/main.py index 1ef51b1dc4c..f55237867d0 100755 --- a/docs/build/main.py +++ b/docs/build/main.py @@ -8,7 +8,7 @@ PROJECT_ROOT = THIS_DIR / '..' / '..' -def main(): +def main() -> int: history = (PROJECT_ROOT / 'HISTORY.md').read_text() history = re.sub(r'#(\d+)', r'[#\1](https://github.com/pydantic/pydantic/issues/\1)', history) history = re.sub(r'(\s)@([\w\-]+)', r'\1[@\2](https://github.com/\2)', history, flags=re.I) diff --git a/docs/build/schema_mapping.py b/docs/build/schema_mapping.py index 96c7b74a01f..60d385b0606 100755 --- a/docs/build/schema_mapping.py +++ b/docs/build/schema_mapping.py @@ -6,107 +6,109 @@ Please edit this file directly not .tmp_schema_mappings.html """ +from __future__ import annotations import json import re from pathlib import Path +from typing import Any -table = [ - [ +table: list[tuple[str, str, str | dict[str, Any], str, str]] = [ + ( 'None', 'null', '', 'JSON Schema Core', - 'Same for `type(None)` or `Literal[None]`' - ], - [ + 'Same for `type(None)` or `Literal[None]`', + ), + ( 'bool', 'boolean', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'str', 'string', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'float', 'number', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'int', 'integer', '', 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'dict', 'object', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'list', 'array', {'items': {}}, 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'tuple', 'array', {'items': {}}, 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'set', 'array', {'items': {}, 'uniqueItems': True}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'frozenset', 'array', {'items': {}, 'uniqueItems': True}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'List[str]', 'array', {'items': {'type': 'string'}}, 'JSON Schema Validation', - 'And equivalently for any other sub type, e.g. `List[int]`.' - ], - [ + 'And equivalently for any other sub type, e.g. `List[int]`.', + ), + ( 'Tuple[str, ...]', 'array', {'items': {'type': 'string'}}, 'JSON Schema Validation', - 'And equivalently for any other sub type, e.g. `Tuple[int, ...]`.' - ], - [ + 'And equivalently for any other sub type, e.g. `Tuple[int, ...]`.', + ), + ( 'Tuple[str, int]', 'array', {'items': [{'type': 'string'}, {'type': 'integer'}], 'minItems': 2, 'maxItems': 2}, 'JSON Schema Validation', ( 'And equivalently for any other set of subtypes. Note: If using schemas for OpenAPI, ' - 'you shouldn\'t use this declaration, as it would not be valid in OpenAPI (although it is ' + "you shouldn't use this declaration, as it would not be valid in OpenAPI (although it is " 'valid in JSON Schema).' - ) - ], - [ + ), + ), + ( 'Dict[str, int]', 'object', {'additionalProperties': {'type': 'integer'}}, @@ -115,247 +117,247 @@ 'And equivalently for any other subfields for dicts. Have in mind that although you can use other types as ' 'keys for dicts with Pydantic, only strings are valid keys for JSON, and so, only str is valid as ' 'JSON Schema key types.' - ) - ], - [ + ), + ), + ( 'Union[str, int]', 'anyOf', {'anyOf': [{'type': 'string'}, {'type': 'integer'}]}, 'JSON Schema Validation', - 'And equivalently for any other subfields for unions.' - ], - [ + 'And equivalently for any other subfields for unions.', + ), + ( 'Enum', 'enum', '{"enum": [...]}', 'JSON Schema Validation', - 'All the literal values in the enum are included in the definition.' - ], - [ + 'All the literal values in the enum are included in the definition.', + ), + ( 'SecretStr', 'string', {'writeOnly': True}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'SecretBytes', 'string', {'writeOnly': True}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'EmailStr', 'string', {'format': 'email'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NameEmail', 'string', {'format': 'name-email'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'AnyUrl', 'string', {'format': 'uri'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'Pattern', 'string', {'format': 'regex'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'bytes', 'string', {'format': 'binary'}, 'OpenAPI', - '' - ], - [ + '', + ), + ( 'Decimal', 'number', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'UUID1', 'string', {'format': 'uuid1'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'UUID3', 'string', {'format': 'uuid3'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'UUID4', 'string', {'format': 'uuid4'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'UUID5', 'string', {'format': 'uuid5'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'UUID', 'string', {'format': 'uuid'}, 'Pydantic standard "format" extension', - 'Suggested in OpenAPI.' - ], - [ + 'Suggested in OpenAPI.', + ), + ( 'FilePath', 'string', {'format': 'file-path'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'DirectoryPath', 'string', {'format': 'directory-path'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'Path', 'string', {'format': 'path'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'datetime', 'string', {'format': 'date-time'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'date', 'string', {'format': 'date'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'time', 'string', {'format': 'time'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'timedelta', 'number', {'format': 'time-delta'}, 'Difference in seconds (a `float`), with Pydantic standard "format" extension', - 'Suggested in JSON Schema repository\'s issues by maintainer.' - ], - [ + "Suggested in JSON Schema repository's issues by maintainer.", + ), + ( 'Json', 'string', {'format': 'json-string'}, 'Pydantic standard "format" extension', - '' - ], - [ + '', + ), + ( 'IPv4Address', 'string', {'format': 'ipv4'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'IPv6Address', 'string', {'format': 'ipv6'}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'IPvAnyAddress', 'string', {'format': 'ipvanyaddress'}, 'Pydantic standard "format" extension', 'IPv4 or IPv6 address as used in `ipaddress` module', - ], - [ + ), + ( 'IPv4Interface', 'string', {'format': 'ipv4interface'}, 'Pydantic standard "format" extension', 'IPv4 interface as used in `ipaddress` module', - ], - [ + ), + ( 'IPv6Interface', 'string', {'format': 'ipv6interface'}, 'Pydantic standard "format" extension', 'IPv6 interface as used in `ipaddress` module', - ], - [ + ), + ( 'IPvAnyInterface', 'string', {'format': 'ipvanyinterface'}, 'Pydantic standard "format" extension', 'IPv4 or IPv6 interface as used in `ipaddress` module', - ], - [ + ), + ( 'IPv4Network', 'string', {'format': 'ipv4network'}, 'Pydantic standard "format" extension', 'IPv4 network as used in `ipaddress` module', - ], - [ + ), + ( 'IPv6Network', 'string', {'format': 'ipv6network'}, 'Pydantic standard "format" extension', 'IPv6 network as used in `ipaddress` module', - ], - [ + ), + ( 'IPvAnyNetwork', 'string', {'format': 'ipvanynetwork'}, 'Pydantic standard "format" extension', 'IPv4 or IPv6 network as used in `ipaddress` module', - ], - [ + ), + ( 'StrictBool', 'boolean', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'StrictStr', 'string', '', 'JSON Schema Core', - '' - ], - [ + '', + ), + ( 'ConstrainedStr', 'string', '', @@ -363,16 +365,16 @@ ( 'If the type has values declared for the constraints, they are included as validations. ' 'See the mapping for `constr` below.' - ) - ], - [ - 'constr(regex=\'^text$\', min_length=2, max_length=10)', + ), + ), + ( + "constr(regex='^text$', min_length=2, max_length=10)", 'string', {'pattern': '^text$', 'minLength': 2, 'maxLength': 10}, 'JSON Schema Validation', - 'Any argument not passed to the function (not defined) will not be included in the schema.' - ], - [ + 'Any argument not passed to the function (not defined) will not be included in the schema.', + ), + ( 'ConstrainedInt', 'integer', '', @@ -380,44 +382,44 @@ ( 'If the type has values declared for the constraints, they are included as validations. ' 'See the mapping for `conint` below.' - ) - ], - [ + ), + ), + ( 'conint(gt=1, ge=2, lt=6, le=5, multiple_of=2)', 'integer', {'maximum': 5, 'exclusiveMaximum': 6, 'minimum': 2, 'exclusiveMinimum': 1, 'multipleOf': 2}, '', - 'Any argument not passed to the function (not defined) will not be included in the schema.' - ], - [ + 'Any argument not passed to the function (not defined) will not be included in the schema.', + ), + ( 'PositiveInt', 'integer', {'exclusiveMinimum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NegativeInt', 'integer', {'exclusiveMaximum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NonNegativeInt', 'integer', {'minimum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NonPositiveInt', 'integer', {'maximum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'ConstrainedFloat', 'number', '', @@ -425,44 +427,44 @@ ( 'If the type has values declared for the constraints, they are included as validations. ' 'See the mapping for `confloat` below.' - ) - ], - [ + ), + ), + ( 'confloat(gt=1, ge=2, lt=6, le=5, multiple_of=2)', 'number', {'maximum': 5, 'exclusiveMaximum': 6, 'minimum': 2, 'exclusiveMinimum': 1, 'multipleOf': 2}, 'JSON Schema Validation', - 'Any argument not passed to the function (not defined) will not be included in the schema.' - ], - [ + 'Any argument not passed to the function (not defined) will not be included in the schema.', + ), + ( 'PositiveFloat', 'number', {'exclusiveMinimum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NegativeFloat', 'number', {'exclusiveMaximum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NonNegativeFloat', 'number', {'minimum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'NonPositiveFloat', 'number', {'maximum': 0}, 'JSON Schema Validation', - '' - ], - [ + '', + ), + ( 'ConstrainedDecimal', 'number', '', @@ -470,29 +472,29 @@ ( 'If the type has values declared for the constraints, they are included as validations. ' 'See the mapping for `condecimal` below.' - ) - ], - [ + ), + ), + ( 'condecimal(gt=1, ge=2, lt=6, le=5, multiple_of=2)', 'number', {'maximum': 5, 'exclusiveMaximum': 6, 'minimum': 2, 'exclusiveMinimum': 1, 'multipleOf': 2}, 'JSON Schema Validation', - 'Any argument not passed to the function (not defined) will not be included in the schema.' - ], - [ + 'Any argument not passed to the function (not defined) will not be included in the schema.', + ), + ( 'BaseModel', 'object', '', 'JSON Schema Core', - 'All the properties defined will be defined with standard JSON Schema, including submodels.' - ], - [ + 'All the properties defined will be defined with standard JSON Schema, including submodels.', + ), + ( 'Color', 'string', {'format': 'color'}, 'Pydantic standard "format" extension', '', - ], + ), ] headings = [ @@ -503,11 +505,11 @@ ] -def md2html(s): +def md2html(s: str) -> str: return re.sub(r'`(.+?)`', r'\1', s) -def build_schema_mappings(): +def build_schema_mappings() -> None: rows = [] for py_type, json_type, additional, defined_in, notes in table: