Repository: HypothesisWorks/hypothesis

Issue:
Guidelines for external packages vs contributions to Hypothesis
Continuing the discussion from #742 so as to not have this on an unrelated issue.

Copying my suggestion from there:


> I don't think that we have a formal policy about strategies. I think roughly the following are the guidelines I've been using:
>
> Either an external package or adding it to Hypothesis is fine for most strategies.
> * If the strategy is likely to be of widespread use, we should encourage adding it to Hypothesis core (and offer to help with the work, as this isn't totally straightforward).
> * If the strategy is likely to be of quite niche use, there's still no problem adding it to Hypothesis core, but we don't need to be quite so enthusiastic about doing so.
> (Plus when adding something to Hypothesis core usual rules about things needing an external dependency going in as hypothesis.extra packages)
> Roughly the trade-off is that it's much easier to get things working outside of Hypothesis because there's more freedom to experiment and fewer requirements. but being inside Hypothesis gets you access to somewhat cooler implementation features and better long-term guarantees about maintenance.

@alexwlchan and @mulkieran I think both felt that this was too far in the direction of include by default I think. Is that a fair characterisation? In particular https://github.com/HypothesisWorks/hypothesis-python/issues/742#issuecomment-318768634 and https://github.com/HypothesisWorks/hypothesis-python/issues/742#issuecomment-318663496

My main reasoning as to why I still think an include by default policy is better is that it stops people who are developing things on top of Hypothesis from having to worry about compatibility with different versions of Hypothesis, and it stops us from worrying about breaking their libraries (as these often tend to use slightly more than the public API). In particular the .extra packages exist in Hypothesis because historically I found keeping multiple versions in sync very annoying.

OTOH the public API is significantly more capable than it was when I merged the .extra packages in, so maybe that's not a big deal? Maybe we *should* be trying to divest more, and just make sure that people can do so entirely through the public API.


Discussion:
(I do think it makes sense to try to include standard library types in Hypothesis core though. in particular I'm still keen on #742 and #705 regardless of what we decide)
I see this as a two-stage process, with this issue focusing on *what kinds of strategies do we want* (rather than implementation).  My general principles:

- Monoliths are great!  `pip install hypothesis` should include everything you need - hunting for extension packages really sucks for new users.  It also means that we catch what would be downstream breakage before it happens, in our own tests, and can do preventative maintenance or refactoring.  Plus, the Hypothesis community is nowhere near large enough to benefit from a split - having a single issue tracker and team is really useful at our scale.

- Composition is great!  If a proposed strategy is reducible to `builds(Thing, *primitives)`, well, we already have `builds()`.  On the other hand, new primitives (highly composable strategies) are very valuable.  I'd also include any strategies which are difficult to implement outside - very complex (eg regex), prone to subtle bugs, where completeness of examples is tricky, or where the default behaviour can encourage good API design by users (eg 'accept path-like objects too').

- We may eventually have too many strategies or too much compatibility, but I think that's a long way away - and we can fix it and bump the major version if this ever does become a big problem.

To summarise - if someone writes a non-trivial strategy, I'd like it to be part of the main package.
AFAICT, all these arguments are valid. It's all in how much _weight_ you give to the various arguments for or against that makes you lean one way or the other.

I didn't have any objection to including the path strategy in core in the first place, and I think it would be rude to take any other position at this time, now that the PR is up by invitation.
> AFAICT, all these arguments are valid. It's all in how much weight you give to the various arguments for or against that makes you lean one way or the other.

Yeah, absolutely. I don't think anyone is really disagreeing with anyone about these factors. It's just a question of trade offs and what our default position should be.

> I think it would be rude to take any other position at this time, now that the PR is up by invitation.

Absolutely agreed, but conveniently that goes in line with what I wanted to do anyway so there's no need to be rude. 😁 

Resolving patch:
diff --git a/src/hypothesis/internal/validation.py b/src/hypothesis/internal/validation.py
--- a/src/hypothesis/internal/validation.py
+++ b/src/hypothesis/internal/validation.py
@@ -135,13 +135,8 @@ def check_valid_sizes(min_size, average_size, max_size):
     check_valid_interval(average_size, max_size, 'average_size', 'max_size')
     check_valid_interval(min_size, average_size, 'min_size', 'average_size')
 
-    if (
-        average_size == 0 and (
-            max_size is None or
-            max_size > 0
-        )
-    ):
+    if average_size == 0 and (max_size is None or max_size > 0):
         raise InvalidArgument(
             'Cannot have average_size=%r with non-zero max_size=%r' % (
-                average_size, min_size
+                average_size, max_size
             ))
diff --git a/src/hypothesis/searchstrategy/strategies.py b/src/hypothesis/searchstrategy/strategies.py
--- a/src/hypothesis/searchstrategy/strategies.py
+++ b/src/hypothesis/searchstrategy/strategies.py
@@ -491,7 +491,7 @@ def do_draw(self, data):
             self.element_strategies[i], label=self.branch_labels[i])
 
     def __repr__(self):
-        return ' | '.join(map(repr, self.original_strategies))
+        return 'one_of(%s)' % ', '.join(map(repr, self.original_strategies))
 
     def do_validate(self):
         for e in self.element_strategies:
diff --git a/src/hypothesis/searchstrategy/strings.py b/src/hypothesis/searchstrategy/strings.py
--- a/src/hypothesis/searchstrategy/strings.py
+++ b/src/hypothesis/searchstrategy/strings.py
@@ -76,7 +76,7 @@ def __init__(self, list_of_one_char_strings_strategy):
         )
 
     def __repr__(self):
-        return 'StringStrategy()'
+        return "%r.map(u''.join)" % self.mapped_strategy
 
     def pack(self, ls):
         return u''.join(ls)
@@ -87,7 +87,8 @@ class BinaryStringStrategy(MappedSearchStrategy):
     lists of bytes."""
 
     def __repr__(self):
-        return 'BinaryStringStrategy()'
+        return '%r.map(bytearray).map(%s)' % (
+            self.mapped_strategy, binary_type.__name__)
 
     def pack(self, x):
         assert isinstance(x, list), repr(x)


Test patch: