Why did you put end_time = end_time or datetime.now()

It works together with having the end_time=None default in the method signature. If the method is not given an end_time, it is None, and then it gets the value of datetime.now().

It is essentially a 1-line version of:

if not end_time: end_time = datetime.now()

You can also use and in a similar way, but with a different meaning. E.g.:

some_value = obj and obj.attribute

I.e., if obj is None, obj.attribute will not get called (which would raise an exception), and some_value gets assigned the None.

This is again a more concise version of:

some_value = obj if obj: some_value = obj.attribute

... or the much uglier (to me):

some_value = obj.attribute if obj else obj

And is this to check to se if the cleaning was started

@property def started(self): return bool(self.start_time)

Yes, but it is not really used in this code, and not very useful either, because the main way to get a cleaning object is with the start_cleaning method, so it is always at least started when you have it.