Files
pydantic/tests/conftest.py
T
Arseny Boykov 6b53cabe03 Add pickle support to dynamically created models and generics (#1686)
* Add pickle support to dynamically created models

* Add created model module check

* Use globals() to retrieve model

* Use single quotes

* use mocker instead of mock

Co-authored-by: PrettyWood <em.jolibois@gmail.com>

* remove unused import

* add test for dynamic forward ref

* move generic test to test_generics.py

* fix test_generic_model_pickle test, add additional check for <locals> in create_model

* fix code style

* resolve issues with global/local models

* make ensure_picklable return given model

* remove ensure_picklable, use test_is_call_from_module only in generics

* reformat code after updating black==20.8b1

* move get_caller_module_name and is_call_from_module to generics

* apply suggestions from @samuelcolvin

* add tests for get_caller_module and is_call_from_module called from module

* fix linting

* fix path to modules in test_module fixture, capture stderr and stdout

* fix broken test module

* fix subprocess call for windows

* enhance create_module fixture, add run_as_module fixture

this will allow run modules in subprocess and define module code in functions-containers

* add test for redefined concrete model without cache

* add changes file

* remove unused variable

* rewrite tests with pytest.raises

* fix linting

* rewrite test_create_model_pickle and test_forward_ref_with_create_model with module definition in function

* Update create_model docstring

Co-authored-by: PrettyWood <em.jolibois@gmail.com>
Co-authored-by: Samuel Colvin <s@muelcolvin.com>
2020-10-18 20:55:44 +01:00

93 lines
2.6 KiB
Python

import inspect
import os
import secrets
import subprocess
import sys
import textwrap
from importlib.machinery import SourceFileLoader
from types import FunctionType
import pytest
def _extract_source_code_from_function(function):
if function.__code__.co_argcount:
raise RuntimeError(f'function {function.__qualname__} cannot have any arguments')
code_lines = ''
body_started = False
for line in textwrap.dedent(inspect.getsource(function)).split('\n'):
if line.startswith('def '):
body_started = True
continue
elif body_started:
code_lines += f'{line}\n'
return textwrap.dedent(code_lines)
def _create_module(code, tmp_path, name):
name = f'{name}_{secrets.token_hex(5)}'
path = tmp_path / f'{name}.py'
path.write_text(code)
return name, path
class SetEnv:
def __init__(self):
self.envars = set()
def set(self, name, value):
self.envars.add(name)
os.environ[name] = value
def clear(self):
for n in self.envars:
os.environ.pop(n)
@pytest.yield_fixture
def env():
setenv = SetEnv()
yield setenv
setenv.clear()
@pytest.fixture
def create_module(tmp_path, request):
def run(module_source_code=None):
"""
Creates module and loads it with SourceFileLoader().load_module()
"""
if isinstance(module_source_code, FunctionType):
module_source_code = _extract_source_code_from_function(module_source_code)
name, path = _create_module(module_source_code, tmp_path, request.node.name)
return SourceFileLoader(name, str(path)).load_module()
return run
@pytest.fixture
def run_as_module(tmp_path, request):
def run(module_source_code=None):
"""
Creates module source and runs it in subprocess
This way is much slower than SourceFileLoader().load_module(),
but executes module as __main__ with a clear stack (https://docs.python.org/3/library/__main__.html)
"""
if isinstance(module_source_code, FunctionType):
module_source_code = _extract_source_code_from_function(module_source_code)
_, path = _create_module(module_source_code, tmp_path, request.node.name)
result = subprocess.run([sys.executable, str(path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5)
if result.returncode != 0:
pytest.fail(
f'Running {path} failed with non-zero return code: {result.returncode}\n'
f'Captured stdout:\n{result.stdout.decode()}\n'
f'Captured stderr:\n{result.stderr.decode()}'
)
return run