ExpectedConditions checks the condition every 500ms (default) until the condition is true or the timeout is reached. If the timeout is reached, a timeout exception is thrown. You don't want to put it in a loop, etc.
In my experience, you should check to see if the spinner is visible first. I've had times where if I'm just checking for the spinner to be invisible that the page loads and the spinner hasn't appeared yet because the page is loading slowly and the condition succeeds, only to have the spinner later popup and cause a failure.
How the method is written really depends on the intent of the method. I've written these in the past to just wait for the spinner to be visible then invisible, as below
def wait_for_spinner(driver):
wait = WebDriverWait(driver, 60)
spinner_locator = (By.XPATH, 'path')
wait.until(EC.visibility_of_element_located(spinner_locator))
wait.until(EC.invisibility_of_element_located(spinner_locator))
I changed the name to wait_for_spinner because that's all it does. If the spinner doesn't go invisible, I want the test to fail because if it doesn't here, then it's going to fail somewhere else and I'm going to have to investigate what happened. If the method above fails, it will likely fail with a TimeoutException. If you want it to fail with an Assert, you could add a try-except around the waits and fail with an assert. I personally let it fail with the TimeoutException because then I know exactly what happened... which wait failed and why but that's a personal preference.
As to how to implement this, you would call wait_for_spinner() on each page load. It *is* DRY because you've housed all the wait code inside a method that's called when needed. There really is no other way. I don't know you're framework but if you have a base class, you could put that call in the constructor of the base class so that it's called automatically. You will likely still have times on certain pages where you trigger some action and the page reloads... you'll need to call wait_for_spinner() again.