Skip to content

Commit

Permalink
Merge pull request HypothesisWorks#1237 from Zac-HD/typed
Browse files Browse the repository at this point in the history
Add basic static typing to the Hypothesis API
  • Loading branch information
Zac-HD committed Apr 22, 2018
2 parents d1460a1 + 42103e2 commit 92f2828
Show file tree
Hide file tree
Showing 25 changed files with 195 additions and 74 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
venv*
.cache
.pytest_cache
.mypy_cache
.hypothesis
docs/_build
*.egg-info
Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ env:
- TASK=doctest
- TASK=check-rst
- TASK=check-format
- TASK=check-type-hints
- TASK=check-coverage
- TASK=check-requirements
- TASK=check-pypy
Expand Down Expand Up @@ -65,7 +66,7 @@ env:
- TASK=deploy

script:
- ./build.sh
- ./build.sh

matrix:
fast_finish: true
Expand Down
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

This patch contains some internal refactoring to run :pypi:`mypy` in CI.
There are no user-visible changes.
1 change: 1 addition & 0 deletions hypothesis-python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def local_file(name):
author_email='david@drmaciver.com',
packages=setuptools.find_packages(SOURCE),
package_dir={'': SOURCE},
# package_data={'': ['py.typed']}, # un-comment to release type hints
url=(
'https://github.com/HypothesisWorks/hypothesis/'
'tree/master/hypothesis-python'
Expand Down
17 changes: 11 additions & 6 deletions hypothesis-python/src/hypothesis/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
from hypothesis.internal.validation import try_convert
from hypothesis.utils.dynamicvariables import DynamicVariable

if False:
from typing import Dict, List # noqa

__all__ = [
'settings',
]
Expand All @@ -46,7 +49,7 @@
unlimited = UniqueIdentifier('unlimited')


all_settings = {}
all_settings = {} # type: Dict[str, Setting]


class settingsProperty(object):
Expand Down Expand Up @@ -113,7 +116,9 @@ def _assign_default_internal(self, value):
default_variable.value = value


class settings(settingsMeta('settings', (object,), {})):
class settings(
settingsMeta('settings', (object,), {}) # type: ignore
):
"""A settings object controls a variety of parameters that are used in
falsification. These may control both the falsification strategy and the
details of the data that is generated.
Expand All @@ -126,7 +131,7 @@ class settings(settingsMeta('settings', (object,), {})):
'_construction_complete', 'storage'
]
__definitions_are_locked = False
_profiles = {}
_profiles = {} # type: dict

def __getattr__(self, name):
if name in all_settings:
Expand Down Expand Up @@ -498,11 +503,11 @@ def _validate_timeout(n):
)


def _validate_database(db, __from_db_file=False):
def _validate_database(db, _from_db_file=False):
from hypothesis.database import ExampleDatabase
if db is None or isinstance(db, ExampleDatabase):
return db
if __from_db_file or db is not_set:
if _from_db_file or db is not_set:
return ExampleDatabase(db)
raise InvalidArgument(
'Arguments to the database setting must be None or an instance of '
Expand Down Expand Up @@ -533,7 +538,7 @@ def _validate_database(db, __from_db_file=False):
The file or directory location to save and load previously tried examples;
`:memory:` for an in-memory cache or None to disable caching entirely.
""",
validator=lambda f: _validate_database(f, __from_db_file=True),
validator=lambda f: _validate_database(f, _from_db_file=True),
deprecation_message="""
The `database_file` setting is deprecated in favor of the `database`
setting, and will be removed in a future version. It only exists at
Expand Down
17 changes: 14 additions & 3 deletions hypothesis-python/src/hypothesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
from coverage.collector import Collector

import hypothesis.strategies as st
from hypothesis import __version__
from hypothesis.errors import Flaky, Timeout, NoSuchExample, \
Unsatisfiable, DidNotReproduce, InvalidArgument, DeadlineExceeded, \
MultipleFailures, FailedHealthCheck, HypothesisWarning, \
UnsatisfiedAssumption, HypothesisDeprecationWarning
from hypothesis.control import BuildContext
from hypothesis.version import __version__
from hypothesis._settings import Phase, Verbosity, HealthCheck, \
PrintSettings
from hypothesis._settings import settings as Settings
Expand Down Expand Up @@ -74,6 +74,9 @@
except ImportError: # pragma: no cover
from coverage.collector import FileDisposition

if False:
from typing import Any, Dict, Callable, Optional # noqa


running_under_pytest = False
global_force_seed = None
Expand Down Expand Up @@ -424,7 +427,7 @@ def __init__(self, filename, source, target):
self.target = target


ARC_CACHE = {}
ARC_CACHE = {} # type: Dict[str, Dict[Any, Dict[Any, Arc]]]


def arc(filename, source, target):
Expand Down Expand Up @@ -890,6 +893,7 @@ def fake_subTest(self, msg=None, **__):


def given(*given_arguments, **given_kwargs):
# type: (*SearchStrategy, **SearchStrategy) -> Callable
"""A decorator for turning a test function that accepts arguments into a
randomized test.
Expand Down Expand Up @@ -1075,7 +1079,14 @@ def wrapped_test(*arguments, **kwargs):
return run_test_with_generator


def find(specifier, condition, settings=None, random=None, database_key=None):
def find(
specifier, # type: SearchStrategy
condition, # type: Callable[[Any], bool]
settings=None, # type: Settings
random=None, # type: Any
database_key=None, # type: bytes
):
# type: (...) -> Any
"""Returns the minimal example from the given strategy ``specifier`` that
matches the predicate function ``condition``."""
settings = settings or Settings(
Expand Down
4 changes: 3 additions & 1 deletion hypothesis-python/src/hypothesis/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ def __call__(self, *args, **kwargs):
return super(EDMeta, self).__call__(*args, **kwargs)


class ExampleDatabase(EDMeta('ExampleDatabase', (object,), {})):
class ExampleDatabase(
EDMeta('ExampleDatabase', (object,), {}) # type: ignore
):
"""Interface class for storage systems.
A key -> multiple distinct values mapping.
Expand Down
7 changes: 6 additions & 1 deletion hypothesis-python/src/hypothesis/internal/charmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
from hypothesis.configuration import tmpdir, storage_directory
from hypothesis.internal.compat import hunichr

if False:
from typing import Dict, Tuple
intervals = Tuple[Tuple[int, int], ...]
cache_type = Dict[Tuple[Tuple[str, ...], int, int, intervals], intervals]


def charmap_file():
return os.path.join(
Expand Down Expand Up @@ -308,7 +313,7 @@ def _query_for_key(key):
return result


limited_category_index_cache = {}
limited_category_index_cache = {} # type: cache_type


def query(
Expand Down
15 changes: 9 additions & 6 deletions hypothesis-python/src/hypothesis/internal/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@
try:
from collections import OrderedDict, Counter
except ImportError: # pragma: no cover
from ordereddict import OrderedDict
from counter import Counter
from ordereddict import OrderedDict # type: ignore
from counter import Counter # type: ignore

if False:
from typing import Type # noqa


PY2 = sys.version_info[0] == 2
Expand Down Expand Up @@ -410,7 +413,7 @@ def __getitem__(self, *args, **kwargs):
else:
return r

__setitem__ = None
__setitem__ = None # type: ignore

def join(self, parts):
result = bytearray()
Expand Down Expand Up @@ -467,15 +470,15 @@ def implements_iterator(it):


if PY3:
FileNotFoundError = FileNotFoundError
FileNotFoundError = FileNotFoundError # type: Type[IOError]
else:
FileNotFoundError = IOError

# We need to know what sort of exception gets thrown when you try to write over
# an existing file where you're not allowed to. This is rather less consistent
# between versions than might be hoped.
if PY3:
FileExistsError = FileExistsError
FileExistsError = FileExistsError # type: Type[IOError]

elif WINDOWS:
FileExistsError = WindowsError
Expand Down Expand Up @@ -519,7 +522,7 @@ def b64decode(s):
from base64 import b64decode


_cases = []
_cases = [] # type: list


def bad_django_TestCase(runner):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class StructuralTag(object):
label = attr.ib()


STRUCTURAL_TAGS = {}
STRUCTURAL_TAGS = {} # type: dict


def structural_tag(label):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ def __init__(self, tag):
self.tag = tag


NEGATED_CACHE = {}
NEGATED_CACHE = {} # type: dict


def negated(tag):
Expand Down
10 changes: 7 additions & 3 deletions hypothesis-python/src/hypothesis/internal/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

from hypothesis.internal.reflection import proxies

if False:
from typing import Set, Dict, Tuple # noqa

"""
This module implements a custom coverage system that records conditions and
then validates that every condition has been seen to be both True and False
Expand All @@ -37,7 +40,7 @@
itself and has essentially no overhead.
"""

pretty_file_name_cache = {}
pretty_file_name_cache = {} # type: Dict[str, str]


def pretty_file_name(f):
Expand All @@ -59,7 +62,7 @@ def pretty_file_name(f):
if IN_COVERAGE_TESTS:
with open('branch-check', 'w'):
pass
written = set()
written = set() # type: Set[Tuple[str, bool]]

def record_branch(name, value):
key = (name, value)
Expand Down Expand Up @@ -109,7 +112,8 @@ def accept(*args, **kwargs):
def check_function(f):
return f

@contextmanager
# Mypy bug: https://github.com/python/mypy/issues/4117
@contextmanager # type: ignore
def check(name):
yield

Expand Down
5 changes: 4 additions & 1 deletion hypothesis-python/src/hypothesis/internal/escalation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
from hypothesis.internal.compat import text_type, binary_type, \
encoded_filepath

if False:
from typing import Dict # noqa


def belongs_to(package):
root = os.path.dirname(package.__file__)
Expand All @@ -50,7 +53,7 @@ def accept(filepath):

PREVENT_ESCALATION = os.getenv('HYPOTHESIS_DO_NOT_ESCALATE') == 'true'

FILE_CACHE = {}
FILE_CACHE = {} # type: Dict[bytes, bool]


is_hypothesis_file = belongs_to(hypothesis)
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/src/hypothesis/internal/reflection.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ def eval_directory():
return storage_directory('eval_source')


eval_cache = {}
eval_cache = {} # type: dict


def source_exec_as_module(source):
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ class ListStrategy(SearchStrategy):
def __init__(self, elements, min_size=0, max_size=float('inf')):
SearchStrategy.__init__(self)
self.min_size = min_size or 0
self.max_size = max_size or float('inf')
assert 0 <= min_size <= max_size
self.max_size = max_size if max_size is not None else float('inf')
assert 0 <= self.min_size <= self.max_size
self.average_size = min(
max(self.min_size * 2, self.min_size + 5),
0.5 * (self.min_size + self.max_size),
Expand Down
6 changes: 5 additions & 1 deletion hypothesis-python/src/hypothesis/searchstrategy/lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
convert_keyword_arguments, convert_positional_arguments
from hypothesis.searchstrategy.strategies import SearchStrategy

unwrap_cache = {}
if False:
from typing import Dict # noqa


unwrap_cache = {} # type: Dict[SearchStrategy, SearchStrategy]
unwrap_depth = 0


Expand Down
Loading

0 comments on commit 92f2828

Please sign in to comment.