Repository: pckroon/hypothesis-networkx

Issue:
Deprecation warning about using empty data for sampled_from
```
____________________________________________________________________________________________ test_graph_builder ____________________________________________________________________________________________

    @settings(max_examples=250, suppress_health_check=[HealthCheck.too_slow])
>   @given(st.data())
    def test_graph_builder(data):
        """
        Make sure the number of nodes and edges of the generated graphs is correct,
        and make sure that graphs that are supposed to be connected are.

../hypothesis-networkx/tests/test_graph_builder.py:30: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/core.py:606: in evaluate_test_data
    result = self.execute(data)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/core.py:571: in execute
    result = self.test_runner(data, run)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/executors.py:56: in default_new_style_executor
    return function(data)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/core.py:569: in run
    return test(*args, **kwargs)
../hypothesis-networkx/tests/test_graph_builder.py:30: in test_graph_builder
    @given(st.data())
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/core.py:530: in test
    result = self.test(*args, **kwargs)
../hypothesis-networkx/tests/test_graph_builder.py:69: in test_graph_builder
    graph = data.draw(strategy)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/_strategies.py:2105: in draw
    result = self.conjecture_data.draw(strategy)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:830: in draw
    return self.__draw(strategy, label=label)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:845: in __draw
    return strategy.do_draw(self)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:156: in do_draw
    return data.draw(self.wrapped_strategy)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:830: in draw
    return self.__draw(strategy, label=label)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:839: in __draw
    return strategy.do_draw(self)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/_strategies.py:1872: in do_draw
    return self.definition(data.draw, *self.args, **self.kwargs)
../hypothesis-networkx/hypothesis_networkx/strategy.py:168: in graph_builder
    graph.add_edges_from((e[0][0], e[0][1], e[1]) for e in draw(edges))
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:824: in draw
    if strategy.is_empty:
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/strategies.py:154: in accept
    recur(self)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/strategies.py:150: in recur
    mapping[strat] = getattr(strat, calculation)(recur)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:92: in calc_is_empty
    return recur(self.wrapped_strategy)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:107: in wrapped_strategy
    unwrapped_args = tuple(unwrap_strategies(s) for s in self.__args)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:107: in <genexpr>
    unwrapped_args = tuple(unwrap_strategies(s) for s in self.__args)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:51: in unwrap_strategies
    result = unwrap_strategies(s.wrapped_strategy)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:107: in wrapped_strategy
    unwrapped_args = tuple(unwrap_strategies(s) for s in self.__args)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:107: in <genexpr>
    unwrapped_args = tuple(unwrap_strategies(s) for s in self.__args)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:51: in unwrap_strategies
    result = unwrap_strategies(s.wrapped_strategy)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:112: in wrapped_strategy
    base = self.function(*self.__args, **self.__kwargs)
../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/_strategies.py:700: in sampled_from
    since="2019-03-12",
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

message = 'sampled_from() with nothing to sample is deprecated and will be an error in a future version.  It currently returns `st.nothing()`, which if unexpected can make parts of a strategy silently vanish.'
since = '2019-03-12', verbosity = Verbosity.normal

    def note_deprecation(message, since, verbosity=None):
        # type: (str, str, Verbosity) -> None
        if verbosity is None:
            verbosity = settings.default.verbosity
        assert verbosity is not None
        if since != "RELEASEDAY":
            date = datetime.datetime.strptime(since, "%Y-%m-%d").date()
            assert datetime.date(2016, 1, 1) <= date
        warning = HypothesisDeprecationWarning(message)
        if verbosity > Verbosity.quiet:
>           warnings.warn(warning, stacklevel=2)
E           hypothesis.errors.HypothesisDeprecationWarning: sampled_from() with nothing to sample is deprecated and will be an error in a future version.  It currently returns `st.nothing()`, which if unexpected can make parts of a strategy silently vanish.

../../../.cache/pypoetry/virtualenvs/bootsteps-py3.7/lib/python3.7/site-packages/hypothesis/_settings.py:801: HypothesisDeprecationWarning
```

Comments:

Patch:
diff --git a/hypothesis_networkx/strategy.py b/hypothesis_networkx/strategy.py
index 5d29848..5d58020 100644
--- a/hypothesis_networkx/strategy.py
+++ b/hypothesis_networkx/strategy.py
@@ -111,36 +111,26 @@ def graph_builder(draw,
         # Shrink towards high index, so shrink to the path graph. Otherwise
         # it'll shrink to the star graph.
         initial_edges = [draw(st.tuples(st.integers(-(n_idx-1), 0).map(lambda x: -x),
-                                        st.just(n_idx),
-                                        edge_data))
+                                        st.just(n_idx)))
                          for n_idx in range(1, len(graph))]
         graph.add_edges_from(initial_edges)
 
-    def edge_filter(idx, jdx):
-        """
-        Helper function to decide whether the edge between idx and jdx can still
-        be added to graph.
-        """
-        # <= because self loops
-        return ((not graph.has_edge(idx, jdx) or is_multigraph) and
-                (idx <= jdx or is_directed) and
-                (idx != jdx or self_loops))
-
-    available_edges = [(idx, jdx) for jdx in graph for idx in graph
-                       if edge_filter(idx, jdx)]
-
     # Now for the mess. The maximum number of edges possible depends on the
     # graph type.
-    if max_edges is not None:
-        # Correct for number of edges already made if graph is connected.
-        # This may mean we added more edges than originally allowed.
-        max_edges -= len(graph.edges)
     if not is_multigraph:
         # Multi(Di)Graphs can make an infinite number of edges. For everything
         # else we clamp the range to (0, max_possible_edges)
-        max_possible_edges = len(available_edges)
+        max_possible_edges = len(graph) * (len(graph) - 1)
+        if is_directed:
+            max_possible_edges *= 2
+        if self_loops:
+            max_possible_edges += len(graph)
         if max_edges is None or max_edges > max_possible_edges:
             max_edges = max_possible_edges
+    if max_edges is not None:
+        # Correct for number of edges already made if graph is connected.
+        # This may mean we added more edges than originally allowed.
+        max_edges -= len(graph.edges)
     if max_edges < 0:
         max_edges = 0
 
@@ -152,35 +142,44 @@ def graph_builder(draw,
     elif min_edges > max_edges:
         min_edges = max_edges
 
+    def edge_filter(edge):
+        """
+        Helper function to decide whether the edge between idx and jdx can still
+        be added to graph.
+        """
+        # <= because self loops
+        idx, jdx = edge
+        return ((not graph.has_edge(idx, jdx) or is_multigraph) and
+                (idx <= jdx or is_directed) and
+                (idx != jdx or self_loops))
+
     # We need to sample a number of items from options, these items are 
     # possibly not unique. In addition, we need to draw the same number of
     # items from edge_data and associate the two. To top it off, uniqueness
     # is defined by the content of the first element of the tuple.
-    if is_multigraph:
-        # This is the recommended way because it shrinks better, but it is
-        # prohibitively slow if too many of the available edge have to be drawn,
-        # and they need to be unique.
-        # See https://github.com/HypothesisWorks/hypothesis/issues/1887
-        edges = st.lists(st.tuples(st.sampled_from(available_edges), edge_data),
-                         # unique_by=None if is_multigraph else lambda e: e[:-1],
-                         min_size=min_edges,
-                         max_size=max_edges)
-        graph.add_edges_from((e[0][0], e[0][1], e[1]) for e in draw(edges))
-    else:
-        # Not the recommended way, but way faster if edges have to be unique
-        # Based on https://github.com/HypothesisWorks/hypothesis/issues/1393#issuecomment-409505039
-        edges = []
-        for _ in range(draw(st.integers(min_edges, max_edges))):
-            idx, jdx = draw(st.sampled_from(available_edges))
-            available_edges.remove((idx, jdx))
-            edges.append((idx, jdx, draw(edge_data)))
-        graph.add_edges_from(edges)
+    edges = st.lists(
+        st.tuples(
+            st.integers(min_value=0, max_value=len(graph) - 1),
+            st.integers(min_value=0, max_value=len(graph) - 1),
+        ).filter(edge_filter),
+        unique=not is_multigraph,
+        min_size=min_edges,
+        max_size=max_edges
+    )
+    graph.add_edges_from(draw(edges))
+
+    edge_datas = draw(st.lists(
+        edge_data,
+        min_size=len(graph.edges),
+        max_size=len(graph.edges))
+    )
+    for edge, data in zip(graph.edges, edge_datas):
+        graph.edges[edge].update(data)
 
     if node_keys is not None:
-        new_idxs = draw(st.lists(node_keys,
-                                 unique=True,
-                                 min_size=len(graph),
-                                 max_size=len(graph)))
-        graph = nx.relabel_nodes(graph, dict(zip(list(graph), new_idxs)))
+        new_idxs = draw(st.sets(node_keys,
+                                min_size=len(graph),
+                                max_size=len(graph)))
+        graph = nx.relabel_nodes(graph, dict(zip(list(graph), list(new_idxs))))
 
     return graph


Test patch:
diff --git a/tests/test_graph_builder.py b/tests/test_graph_builder.py
index 7ddf052..5ca3504 100644
--- a/tests/test_graph_builder.py
+++ b/tests/test_graph_builder.py
@@ -157,7 +157,3 @@ def test_negative_max_edges(data):
 
     assert len(graph.edges) == 0
     assert len(graph.nodes) <= 5
-
-
-if __name__ == '__main__':
-    test_graph_builder()