-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
30b40f3
commit bf9a69c
Showing
10 changed files
with
807 additions
and
69 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,40 @@ | ||
from __future__ import annotations | ||
|
||
import typing | ||
|
||
Predicate = typing.Callable[[object], bool] | ||
|
||
|
||
class Conjecture: | ||
def __init__(self, predicate: typing.Optional[Predicate] = None) -> None: | ||
self._predicate = predicate or self.predicate | ||
|
||
def predicate(self, value: object) -> bool: | ||
raise NotImplementedError() | ||
|
||
def __eq__(self, other: object) -> bool: | ||
return self._predicate(other) | ||
|
||
def __ne__(self, other: object) -> bool: | ||
return not self._predicate(other) | ||
|
||
def __or__(self, other) -> Conjecture: | ||
return any_of(self, other) | ||
|
||
def __and__(self, other) -> Conjecture: | ||
return all_of(self, other) | ||
|
||
def __invert__(self) -> Conjecture: | ||
return Conjecture(lambda value: not self._predicate(value)) | ||
|
||
|
||
class AnyConjecture(Conjecture): | ||
def __init__(self, conjectures: typing.Iterable[Conjecture]) -> None: | ||
super().__init__() | ||
self.conjectures = conjectures | ||
|
||
def predicate(self, value: object) -> None: | ||
for other in self.conjectures: | ||
if value == other: | ||
return True | ||
|
||
return False | ||
|
||
|
||
class AllConjecture(Conjecture): | ||
def __init__(self, conjectures: typing.Iterable[Conjecture]) -> None: | ||
super().__init__() | ||
self.conjectures = conjectures | ||
|
||
def predicate(self, value: object) -> None: | ||
for other in self.conjectures: | ||
if value != other: | ||
return False | ||
|
||
return True | ||
|
||
|
||
def has(predicate: Predicate): | ||
return Conjecture(predicate) | ||
|
||
|
||
def any_of(*conjectures: Conjecture) -> Conjecture: | ||
return AnyConjecture(conjectures) | ||
|
||
|
||
def all_of(*conjectures: Conjecture) -> Conjecture: | ||
return AllConjecture(conjectures) | ||
""" | ||
conjecture | ||
a pythonic assertion framework | ||
""" | ||
from __future__ import annotations | ||
|
||
def is_between(minimum, maximum) -> Conjecture: | ||
return Conjecture(lambda value: minimum <= value <= maximum) | ||
from conjecture.base import AllOfConjecture, AnyOfConjecture, Conjecture | ||
from conjecture.general import all_of, any_of, anything, has, none | ||
from conjecture.object import instance_of | ||
from conjecture.rich import ( | ||
equal_to, | ||
greater_than, | ||
greater_than_or_equal_to, | ||
less_than, | ||
less_than_or_equal_to, | ||
) | ||
from conjecture.sized import empty, length | ||
from conjecture.string import ends_with, starts_with | ||
|
||
__all__ = ( | ||
"Conjecture", | ||
"AnyOfConjecture", | ||
"AllOfConjecture", | ||
"all_of", | ||
"any_of", | ||
"anything", | ||
"empty", | ||
"ends_with", | ||
"equal_to", | ||
"greater_than_or_equal_to", | ||
"greater_than", | ||
"has", | ||
"instance_of", | ||
"length", | ||
"less_than_or_equal_to", | ||
"less_than", | ||
"none", | ||
"starts_with", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
""" | ||
base conjecture classes | ||
""" | ||
from __future__ import annotations | ||
|
||
import collections.abc | ||
import typing | ||
|
||
Proof = typing.Callable[[object], bool] | ||
|
||
|
||
class Conjecture: | ||
""" | ||
A conjecture describing another object | ||
Conjectures can be used to describe an object to which they are to be compared | ||
against. They assert equality when the proof function resolves truely. | ||
>>> assert 5 == conjecture.greater_than(0) & conjecture.less_than(10) | ||
There are many helpful conjecture factories with their proofs already defined. | ||
:param proof: a callback that asserts some small fact of the passed object | ||
""" | ||
|
||
def __init__(self, proof: typing.Optional[Proof] = None) -> None: | ||
self._proof = proof | ||
|
||
def resolve(self, value: object) -> bool: | ||
""" | ||
Resolve conjecture | ||
This is an abstract method can either be overwritten in a subclass or by | ||
providing a proof method to the constructor. | ||
:param value: the value the conjecture is evaluated against | ||
:return: whether the conjecture resolved truely | ||
""" | ||
|
||
if not self._proof: | ||
raise NotImplementedError() | ||
|
||
return self._proof(value) | ||
|
||
def __eq__(self, other: object) -> bool: | ||
""" | ||
Resolve conjecture via equality | ||
A conjecture can be resolved via `==` or `!=` comparison operators. | ||
:param other: the value the conjecture is evaluated against | ||
:return: whether the conjecture resolved truely | ||
""" | ||
|
||
return self.resolve(other) | ||
|
||
def __invert__(self) -> Conjecture: | ||
""" | ||
Invert conjecture | ||
Invert the resolution of a conjecture | ||
:return: the inverse conjecture | ||
""" | ||
|
||
return Conjecture(lambda value: not self.resolve(value)) | ||
|
||
def __or__(self, other: Conjecture) -> Conjecture: | ||
""" | ||
Combine using any_of | ||
:param other: another conjecture | ||
:return: a conjecture that either of the combined conjectures will | ||
resolve truely | ||
""" | ||
|
||
if not isinstance(other, Conjecture): | ||
raise ValueError(f"Conjecture cannot be combined with {other!r}") | ||
|
||
return AnyOfConjecture((self, other)) | ||
|
||
def __and__(self, other: Conjecture) -> Conjecture: | ||
""" | ||
Combine using all_of | ||
:param other: another conjecture | ||
:return: a conjecture that both of the combined conjectures will resolve truely | ||
""" | ||
|
||
if not isinstance(other, Conjecture): | ||
raise ValueError(f"Conjecture cannot be combined with {other!r}") | ||
|
||
return AllOfConjecture((self, other)) | ||
|
||
|
||
class AnyOfConjecture(Conjecture): | ||
""" | ||
Any of Conjecture | ||
An any of conjecture will resolve truely if any of the passed conjectures | ||
resolve truely themselves. | ||
:param conjectures: a tuple of conjectures | ||
""" | ||
|
||
# pylint: disable=too-few-public-methods | ||
|
||
def __init__(self, conjectures: collections.abc.Iterable[Conjecture]) -> None: | ||
super().__init__() | ||
self.conjectures = conjectures | ||
|
||
def resolve(self, value: object) -> bool: | ||
for other in self.conjectures: | ||
if value == other: | ||
return True | ||
|
||
return False | ||
|
||
|
||
class AllOfConjecture(Conjecture): | ||
""" | ||
All of Conjecture | ||
An all of conjecture will resolve truely only when all of the passed conjectures | ||
resolve truely themselves. | ||
:param conjectures: a tuple of conjectures | ||
""" | ||
|
||
# pylint: disable=too-few-public-methods | ||
|
||
def __init__(self, conjectures: collections.abc.Iterable[Conjecture]) -> None: | ||
super().__init__() | ||
self.conjectures = conjectures | ||
|
||
def resolve(self, value: object) -> bool: | ||
for other in self.conjectures: | ||
if value != other: | ||
return False | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
""" | ||
general conjectures | ||
""" | ||
import conjecture.base | ||
|
||
|
||
def none() -> conjecture.base.Conjecture: | ||
""" | ||
None | ||
Propose that the value is None | ||
>>> assert value == conjecture.none() | ||
:return: a conjecture object | ||
""" | ||
|
||
return conjecture.base.Conjecture(lambda x: x is None) | ||
|
||
|
||
def anything() -> conjecture.base.Conjecture: | ||
""" | ||
Anything | ||
Propose that the value meerly exists | ||
>>> assert value == conjecture.anything() | ||
:return: a conjecture object | ||
""" | ||
|
||
return conjecture.base.Conjecture(lambda x: True) | ||
|
||
|
||
def any_of(*conjectures: conjecture.base.Conjecture) -> conjecture.base.Conjecture: | ||
""" | ||
Any of | ||
Propose any of the conjectures resolve truely | ||
>>> assert value == conjecture.any_of(conjecture1, conjecture2) | ||
:return: a conjecture object | ||
""" | ||
return conjecture.base.AnyOfConjecture(conjectures) | ||
|
||
|
||
def all_of(*conjectures: conjecture.base.Conjecture) -> conjecture.base.Conjecture: | ||
""" | ||
All of | ||
Propose all of the conjectures resolve truely | ||
>>> assert value == conjecture.any_of(conjecture1, conjecture2) | ||
:return: a conjecture object | ||
""" | ||
return conjecture.base.AllOfConjecture(conjectures) | ||
|
||
|
||
def has(proof: conjecture.base.Proof) -> conjecture.base.Conjecture: | ||
""" | ||
Has | ||
Propose a custom proof function | ||
>>> assert value == conjecture.has(lambda x: x > 5) | ||
:return: a conjecture object | ||
""" | ||
return conjecture.base.Conjecture(proof) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
""" | ||
object conjectures | ||
""" | ||
import typing | ||
|
||
import conjecture.base | ||
|
||
|
||
def instance_of( | ||
value: typing.Union[tuple[type, ...], type] | ||
) -> conjecture.base.Conjecture: | ||
""" | ||
Instance of | ||
Propose that value is instance of the provided type(s) | ||
>>> assert value == conjecture.instance_of((str, int)) | ||
:param value: a type or tuple of types to check | ||
:return: a conjecture object | ||
""" | ||
|
||
return conjecture.base.Conjecture(lambda x: isinstance(x, value)) |
Empty file.
Oops, something went wrong.