Skip to content

Commit

Permalink
Merge pull request #3908 from wangkev/pandas-type-annotations
Browse files Browse the repository at this point in the history
Add type annotations
  • Loading branch information
Zac-HD committed Mar 10, 2024
2 parents 9041789 + 6f6b713 commit 80375e0
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 185 deletions.
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

This patch implements type annotations for :func:`~hypothesis.extra.pandas.column`.
5 changes: 2 additions & 3 deletions hypothesis-python/docs/strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,15 @@ See :issue:`3086` for details, e.g. if you're interested in writing your own bac
what that should eventually look like, and we're likely to make regular breaking
changes for some time to come)

Using the prototype :pypi:`crosshair-tool` backend `via this plugin
<https://github.com/pschanely/hypothesis-crosshair>`__,
Using the prototype :pypi:`crosshair-tool` backend via :pypi:`hypothesis-crosshair`,
a solver-backed test might look something like:

.. code-block:: python
from hypothesis import given, settings, strategies as st
@settings(backend="crosshair")
@settings(backend="crosshair") # pip install hypothesis[crosshair]
@given(st.integers())
def test_needs_solver(x):
assert x != 123456789
75 changes: 66 additions & 9 deletions hypothesis-python/src/hypothesis/extra/pandas/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from collections import OrderedDict, abc
from copy import copy
from datetime import datetime, timedelta
from typing import Any, List, Optional, Sequence, Set, Union
from typing import Any, Generic, List, Optional, Sequence, Set, Union, overload

import attr
import numpy as np
Expand Down Expand Up @@ -357,8 +357,8 @@ def result(draw):
return result()


@attr.s(slots=True)
class column:
@attr.s(slots=True, init=False)
class column(Generic[Ex]):
"""Data object for describing a column in a DataFrame.
Arguments:
Expand All @@ -375,11 +375,68 @@ class column:
* unique: If all values in this column should be distinct.
"""

name = attr.ib(default=None)
elements = attr.ib(default=None)
dtype = attr.ib(default=None, repr=get_pretty_function_description)
fill = attr.ib(default=None)
unique = attr.ib(default=False)
name: Optional[Union[str, int]] = attr.ib(default=None)
elements: Optional[st.SearchStrategy[Ex]] = attr.ib(default=None)
dtype: Any = attr.ib(default=None, repr=get_pretty_function_description)
fill: Optional[st.SearchStrategy[Ex]] = attr.ib(default=None)
unique: bool = attr.ib(default=False)

@overload
def __init__(
self: "column[Any]",
name: Optional[Union[str, int]] = ...,
elements: None = ...,
dtype: Any = ...,
fill: None = ...,
unique: bool = ..., # noqa: FBT001
) -> None: ...

@overload
def __init__(
self: "column[Ex]",
name: Optional[Union[str, int]] = ...,
elements: Optional[st.SearchStrategy[Ex]] = ...,
dtype: Any = ...,
fill: Optional[st.SearchStrategy[Ex]] = ...,
unique: bool = ..., # noqa: FBT001
) -> None: ...

def __init__(
self,
name: Optional[Union[str, int]] = None,
elements: Optional[st.SearchStrategy[Ex]] = None,
dtype: Any = None,
fill: Optional[st.SearchStrategy[Ex]] = None,
unique: bool = False, # noqa: FBT001,FBT002
) -> None:
super().__init__()
self.name = name
self.elements = elements
self.dtype = dtype
self.fill = fill
self.unique = unique


@overload
def columns(
names_or_number: Union[int, Sequence[str]],
*,
dtype: Any = ...,
elements: None = ...,
fill: None = ...,
unique: bool = ...,
) -> List[column[Any]]: ...


@overload
def columns(
names_or_number: Union[int, Sequence[str]],
*,
dtype: Any = ...,
elements: Optional[st.SearchStrategy[Ex]] = ...,
fill: Optional[st.SearchStrategy[Ex]] = ...,
unique: bool = ...,
) -> List[column[Ex]]: ...


def columns(
Expand All @@ -389,7 +446,7 @@ def columns(
elements: Optional[st.SearchStrategy[Ex]] = None,
fill: Optional[st.SearchStrategy[Ex]] = None,
unique: bool = False,
) -> List[column]:
) -> List[column[Ex]]:
"""A convenience function for producing a list of :class:`column` objects
of the same general shape.
Expand Down
30 changes: 11 additions & 19 deletions hypothesis-python/tests/cover/test_database_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,23 @@ def test_default_database_is_in_memory():
assert isinstance(ExampleDatabase(), InMemoryExampleDatabase)


def test_default_on_disk_database_is_dir(tmpdir):
def test_default_on_disk_database_is_dir(tmp_path):
assert isinstance(
ExampleDatabase(tmpdir.join("foo")), DirectoryBasedExampleDatabase
ExampleDatabase(tmp_path.joinpath("foo")), DirectoryBasedExampleDatabase
)


def test_selects_directory_based_if_already_directory(tmpdir):
path = str(tmpdir.join("hi.sqlite3"))
DirectoryBasedExampleDatabase(path).save(b"foo", b"bar")
assert isinstance(ExampleDatabase(path), DirectoryBasedExampleDatabase)


def test_does_not_error_when_fetching_when_not_exist(tmpdir):
db = DirectoryBasedExampleDatabase(tmpdir.join("examples"))
def test_does_not_error_when_fetching_when_not_exist(tmp_path):
db = DirectoryBasedExampleDatabase(tmp_path / "examples")
db.fetch(b"foo")


@pytest.fixture(scope="function", params=["memory", "directory"])
def exampledatabase(request, tmpdir):
def exampledatabase(request, tmp_path):
if request.param == "memory":
return ExampleDatabase()
assert request.param == "directory"
return DirectoryBasedExampleDatabase(str(tmpdir.join("examples")))
return DirectoryBasedExampleDatabase(tmp_path / "examples")


def test_can_delete_a_key_that_is_not_present(exampledatabase):
Expand Down Expand Up @@ -120,20 +114,18 @@ def test_an_absent_value_is_present_after_it_moves_to_self(exampledatabase):
assert next(exampledatabase.fetch(b"a")) == b"b"


def test_two_directory_databases_can_interact(tmpdir):
path = str(tmpdir)
db1 = DirectoryBasedExampleDatabase(path)
db2 = DirectoryBasedExampleDatabase(path)
def test_two_directory_databases_can_interact(tmp_path):
db1 = DirectoryBasedExampleDatabase(tmp_path)
db2 = DirectoryBasedExampleDatabase(tmp_path)
db1.save(b"foo", b"bar")
assert list(db2.fetch(b"foo")) == [b"bar"]
db2.save(b"foo", b"bar")
db2.save(b"foo", b"baz")
assert sorted(db1.fetch(b"foo")) == [b"bar", b"baz"]


def test_can_handle_disappearing_files(tmpdir, monkeypatch):
path = str(tmpdir)
db = DirectoryBasedExampleDatabase(path)
def test_can_handle_disappearing_files(tmp_path, monkeypatch):
db = DirectoryBasedExampleDatabase(tmp_path)
db.save(b"foo", b"bar")
base_listdir = os.listdir
monkeypatch.setattr(
Expand Down
18 changes: 8 additions & 10 deletions hypothesis-python/tests/cover/test_filestorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,16 @@ def test_defaults_to_the_default():
assert fs.storage_directory() == fs.__hypothesis_home_directory_default


def test_can_set_homedir_and_it_will_exist(tmpdir):
fs.set_hypothesis_home_dir(str(tmpdir.mkdir("kittens")))
d = fs.storage_directory()
assert "kittens" in str(d)
assert d.exists()
def test_can_set_homedir(tmp_path):
fs.set_hypothesis_home_dir(tmp_path)
assert fs.storage_directory("kittens") == tmp_path / "kittens"


def test_will_pick_up_location_from_env(monkeypatch, tmpdir):
monkeypatch.setattr(os, "environ", {"HYPOTHESIS_STORAGE_DIRECTORY": str(tmpdir)})
assert fs.storage_directory() == tmpdir
def test_will_pick_up_location_from_env(monkeypatch, tmp_path):
monkeypatch.setattr(os, "environ", {"HYPOTHESIS_STORAGE_DIRECTORY": str(tmp_path)})
assert fs.storage_directory() == tmp_path


def test_storage_directories_are_not_created_automatically(tmpdir):
fs.set_hypothesis_home_dir(str(tmpdir))
def test_storage_directories_are_not_created_automatically(tmp_path):
fs.set_hypothesis_home_dir(tmp_path)
assert not fs.storage_directory("badgers").exists()
2 changes: 1 addition & 1 deletion hypothesis-python/tests/cover/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def test_settings_as_decorator_must_be_on_callable():

@skipif_emscripten
def test_puts_the_database_in_the_home_dir_by_default(tmp_path):
script = tmp_path.joinpath("assertlocation.py")
script = tmp_path / "assertlocation.py"
script.write_text(ASSERT_DATABASE_PATH, encoding="utf-8")
subprocess.check_call([sys.executable, str(script)])

Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/pytest/test_pytest_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_is_running_under_pytest():


def test_is_not_running_under_pytest(tmp_path):
pyfile = tmp_path.joinpath("test.py")
pyfile = tmp_path / "test.py"
pyfile.write_text(FILE_TO_RUN, encoding="utf-8")
subprocess.check_call([sys.executable, str(pyfile)])

Expand Down
4 changes: 2 additions & 2 deletions hypothesis-python/tests/pytest/test_seeding.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ def test_failure(i):
"""


def test_repeats_healthcheck_when_following_seed_instruction(testdir, tmpdir):
def test_repeats_healthcheck_when_following_seed_instruction(testdir, tmp_path):
health_check_test = HEALTH_CHECK_FAILURE.replace(
"<file>", repr(str(tmpdir.join("seen")))
"<file>", repr(str(tmp_path / "seen"))
)

script = testdir.makepyfile(health_check_test)
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ addopts =
--strict-markers
--tb=native
-p pytester
-p no:legacypath
--runpytest=subprocess
--durations=20
--durations-min=1.0
Expand Down

0 comments on commit 80375e0

Please sign in to comment.