Skip to content

Commit 44f5741

Browse files
authored
feat: add has_key conjecture
1 parent 2122347 commit 44f5741

File tree

5 files changed

+157
-1
lines changed

5 files changed

+157
-1
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ Matching anything.
5252
>>> assert "abc" == conjecture.anything()
5353
```
5454

55+
#### Mapping
56+
57+
Matching keys.
58+
59+
```pycon
60+
>>> import conjecture
61+
>>> assert {"a": 1} == conjecture.has_key("a")
62+
>>> assert {"a": 1} == conjecture.has_key("a", of=1)
63+
>>> assert {"a": 1} == conjecture.has_key("a", of=conjecture.less_than(5))
64+
```
65+
5566
#### Object
5667

5768
Matching instances of a class.

src/conjecture/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from conjecture.base import AllOfConjecture, AnyOfConjecture, Conjecture
88
from conjecture.general import all_of, any_of, anything, has, none
9+
from conjecture.mapping import has_key
910
from conjecture.object import equal_to, has_attribute, instance_of
1011
from conjecture.rich import (
1112
greater_than,
@@ -29,6 +30,7 @@
2930
"greater_than",
3031
"has",
3132
"has_attribute",
33+
"has_key",
3234
"instance_of",
3335
"length_of",
3436
"less_than_or_equal_to",

src/conjecture/mapping.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""mapping conjectures."""
2+
3+
import collections.abc
4+
5+
import conjecture.base
6+
7+
sentinel = object()
8+
9+
10+
def has_key(value: str, of: object = sentinel) -> conjecture.base.Conjecture:
11+
"""
12+
Has attribute.
13+
14+
Propose that the value has the given key
15+
16+
>>> assert value == conjecture.has_key("foo")
17+
>>> assert value == conjecture.has_key("foo", of=5)
18+
>>> assert value == conjecture.has_key("foo", of=conjecture.less_than(10))
19+
20+
:param value: the name of the key
21+
:param of: an optional value or conjecture to compare the key value against
22+
23+
:return: a conjecture object
24+
"""
25+
# pylint: disable=invalid-name
26+
# of is a perfectly valid name.
27+
28+
if of is sentinel:
29+
return conjecture.base.Conjecture(
30+
lambda x: isinstance(x, collections.abc.Mapping) and value in x
31+
)
32+
33+
return conjecture.base.Conjecture(
34+
lambda x: isinstance(x, collections.abc.Mapping) and x.get(value) == of
35+
)

src/conjecture/object.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def has_attribute(value: str, of: object = sentinel) -> conjecture.base.Conjectu
5555
# pylint: disable=invalid-name
5656
# of is a perfectly valid name.
5757

58-
if of == sentinel:
58+
if of is sentinel:
5959
return conjecture.base.Conjecture(lambda x: hasattr(x, value))
6060

6161
return conjecture.base.Conjecture(lambda x: getattr(x, value, sentinel) == of)

tests/unit/test_has_key.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Tests for :meth:`conjecture.has_key`.
3+
"""
4+
5+
import string
6+
7+
import hypothesis
8+
import hypothesis.strategies as st
9+
10+
import conjecture
11+
12+
13+
@hypothesis.given(
14+
value=st.text(alphabet=string.ascii_letters, min_size=1),
15+
other=st.integers(),
16+
)
17+
def test_should_match_when_key_exists(value: str, other: int) -> None:
18+
"""
19+
has_key() should match when key exists.
20+
"""
21+
mapping = {value: other}
22+
23+
assert conjecture.has_key(value).resolve(mapping)
24+
25+
26+
@hypothesis.given(
27+
value=st.text(alphabet=string.ascii_letters, min_size=1),
28+
key=st.text(alphabet=string.ascii_letters, min_size=1),
29+
other=st.integers(),
30+
)
31+
def test_should_not_match_when_key_doesnt_exists(
32+
value: str,
33+
key: str,
34+
other: int,
35+
) -> None:
36+
"""
37+
has_key() should not match when key doesn't exist.
38+
"""
39+
hypothesis.assume(value != key)
40+
41+
mapping = {key: other}
42+
43+
assert not conjecture.has_key(value).resolve(mapping)
44+
45+
46+
@hypothesis.given(
47+
value=st.text(alphabet=string.ascii_letters, min_size=1),
48+
other=st.integers(),
49+
)
50+
def test_should_match_when_key_value_matches(value: str, other: int) -> None:
51+
"""
52+
has_key() should match when key value matches.
53+
"""
54+
mapping = {value: other}
55+
56+
assert conjecture.has_key(value, of=other).resolve(mapping)
57+
58+
59+
@hypothesis.given(
60+
value=st.text(alphabet=string.ascii_letters, min_size=1),
61+
wrong=st.integers(),
62+
other=st.integers(),
63+
)
64+
def test_should_not_match_when_key_value_doesnt_match(
65+
value: str,
66+
wrong: int,
67+
other: int,
68+
) -> None:
69+
"""
70+
has_key() should not match when key value doesn't match.
71+
"""
72+
hypothesis.assume(wrong != other)
73+
74+
mapping = {value: wrong}
75+
76+
assert not conjecture.has_key(value, of=other).resolve(mapping)
77+
78+
79+
@hypothesis.given(
80+
value=st.text(alphabet=string.ascii_letters, min_size=1),
81+
other=st.integers(),
82+
)
83+
def test_should_match_when_key_value_matches_conjecture(value: str, other: int) -> None:
84+
"""
85+
has_key() should match when key value matches conjecture.
86+
"""
87+
mapping = {value: other}
88+
89+
always = conjecture.has(lambda x: True)
90+
91+
assert conjecture.has_key(value, of=always).resolve(mapping)
92+
93+
94+
@hypothesis.given(
95+
value=st.text(alphabet=string.ascii_letters, min_size=1),
96+
other=st.integers(),
97+
)
98+
def test_should_not_match_when_key_value_doesnt_match_conjecture(
99+
value: str, other: int
100+
) -> None:
101+
"""
102+
has_key() should not match when key value doesn't match conjecture.
103+
"""
104+
mapping = {value: other}
105+
106+
never = conjecture.has(lambda x: False)
107+
108+
assert not conjecture.has_key(value, of=never).resolve(mapping)

0 commit comments

Comments
 (0)