I have an abstract class DataWriter that defines an abstract method write(). This class is supposed to be the base class for a dynamic set of concrete classes, each one of which is intended to implement its own version of the method write(). In order to define the datatype of parameter meta of the method write(), I created the type WriterMeta as follows:
WriterMeta = typing.Union[GSheetWritable, S3Writable, LocalWritable]
Each concrete class would be in charge of processing one of the different types that conform to the union, but linter mypy doesn't seem to grasp that, because when I define the signature of the method write() of the concrete class using one of the types of the union for parameter meta, it marks a Liskov substituion principle violation, which I believe doesn't exist because the concret class is a subset of the abstract class, which means that the parent class can substitute the child class with no problem.
This is my code:
class LocalWritable(typing.TypedDict):
file_name: str
class GSheetWritable(typing.TypedDict):
tab_name: str
class S3Writable(typing.TypedDict):
data_name: str
table_name: str
WriterMeta = typing.Union[GSheetWritable, S3Writable, LocalWritable]
class GSheetOutputWriter(DataWriter):
def __init__(
self, google_driver: GoogleApiDriver, folder: str, settings, timestamp, env
):
self._connector = google_driver
self.folder = folder
self.settings = settings
self.timestamp = timestamp
self.env = env
self.file_name = self.get_file_name()
self._target = self.create_gsheet()
self.new = True
def get_file_name(self) -> str:
file_name = (
"boxes_shipping_costs_"
+ self.settings["volume_source"]
+ "_"
+ (
self.timestamp
if self.settings["volume_source"] == "adhoc"
else self.settings["scm_week"]
)
)
return file_name
def create_gsheet(self):
gsheet = self.connector.sheet_create(self.file_name, folder_id=self.folder)
gsheet.worksheet("Sheet1").resize(rows=1, cols=1)
return gsheet
@property
def connector(self) -> typing.Any:
return self._connector
@property
def target(self) -> typing.Any:
return self._target
def write(self, data: pd.DataFrame, meta: GSheetWritable, versionize: bool):
data = data.replace({np.nan: 0, np.Inf: "Inf"})
print("Writing '{}' table to gsheet.".format(meta["tab_name"]))
if self.new:
tab = self.connector.get_worksheet(self.target.url, "Sheet1")
self.connector.rename_worksheet(tab, meta["tab_name"])
self.new = False
else:
tab = self.connector.add_worksheet(
self.target, meta["tab_name"], rows=1, cols=1
)
time.sleep(random.randint(30, 60))
self.connector.update_worksheet(
tab, [data.columns.values.tolist()] + data.values.tolist()
)
Is my understanding of Liskov sustitution principle right? How can I refactor this set of classes for mypy to accept them?