Tic-Tac-Toe Recursive Solver
Some remarks:


def is_valid_location(self, location):
    return location &gt;= 0 and location &lt; self.board_length



In contrast to many other programming languages, this can be expressed
in Python more succinctly as a chained comparison:

def is_valid_location(self, location):
    return 0 &lt;= location &lt; self.board_length


However, that function seems unnecessary to me, since nowhere in your
program an invalid location is created.



The winning combinations are not dependent on the particular 
instance of a board, it is more logical to define them as a
class variable:

class Board():
    winning_combinations = [
            [0,1,2], [3,4,5], [6,7,8],
            [0,3,6], [1,4,7], [2,5,8],
            [0,4,8], [2,4,6]]

    def __init__(self):
        self.board_length = 9;
        self.squares = [NONE for idx in range(self.board_length)]

    // ...




The efficiency of the get_winner function can be improved.
It is not necessary to check both players against each winning combination:

def get_winner(self):
    for combo in self.winning_combinations:
        player = self.squares[combo[0]]
        if player is not None and self.squares[combo[1]] == player and self.squares[combo[2]] == player:
            return player
    return NONE




The only purpose of  _ = 1 in


    if board.get_winner() == X:
        #print(board)
        _ = 1
    else:
        find_winners(board, depth + 1, O if player == X else X)



seems to be to avoid an empty if-block. Python has a pass statement
for that purpose:

    if board.get_winner() == X:
        #print(board)
        pass
    else:
        find_winners(board, depth + 1, O if player == X else X)




You define three variables

X, O, NONE = ("X", "O", ".")


for the possible states of a cell. A better way to express mutually
exclusive values is an enumeration:

from enum import Enum

class Cell(Enum):
    X = "X"
    O = "O"
    empty = "."