code: def _next(self, possible_solution):
          
          # bail out if we have seen it already. See __iter__ to where seen is initially set.
          # A complete solution has all its variables set to a particular value.
          is_complete = (len(possible_solution) == len(self._vars))
  
          if is_complete:
              self._solutions_seen += 1
              if self.satisfies_constraints(possible_solution):
                  yield dict(possible_solution)
          else:
              if self.is_feasible(possible_solution):
                  for s in self.derived_solutions(possible_solution):
                      for solution in self._next(s):
                          yield solution
docs: Where the magic happens. Produces a generator that returns all solutions given
          a base solution to start searching.