diff --git a/src/store.py b/src/store.py index f008ea0..e773a8e 100644 --- a/src/store.py +++ b/src/store.py @@ -35,7 +35,9 @@ class load_or_create: plain_text=False ): self.load=with_filtered_args(load)[0] + self.load_arg_names = {p.name for p in inspect.signature(load).parameters.values()} self.save=with_filtered_args(save)[0] + self.save_arg_names = {p.name for p in inspect.signature(save).parameters.values()} self.path_fn=path_fn self.hash_len=hash_len self.save_args=save_args @@ -53,35 +55,66 @@ class inner(load_or_create): def __call__(self, *args, **kwargs): # Store the keyword arguments into json and hash it to get the storage path. - json_args = self.to_json(**kwargs) path = self.path(*args, **kwargs) - # path = self.path(hash) - # If the storage path exists, load the cached object from there. - if path.exists(): - with open(path, "r" if self.plain_text else "rb") as file: - # If specified, the first line is a json of the keyword arguments. - if self.save_args: - file.readline() - return self.load(**self.args_to_kwargs(args, kwargs, path=path, file=file)) + obj = self.load_wrapper(**self.args_to_kwargs(args, kwargs, path=path)) + if obj is not None: return obj - # Else, run the function. and store its result. obj = self.fn(*args, **kwargs) + if obj is None: return obj - if obj is None: - return obj - - path.parent.mkdir(parents=True, exist_ok=True) - with open(path, "w" if self.plain_text else "wb") as file: - if self.save_args: - file.write(json_args) - self.save(obj, **self.args_to_kwargs(args, kwargs, path=path, file=file)) + self.save_wrapper(obj, **self.args_to_kwargs(args, kwargs, path=path)) return obj def args_to_kwargs(self, args, kwargs, **extra): return extra | kwargs | {self.arg_names[i]: a for i, a in enumerate(args)} + def load_wrapper(self, **load_args): + """ + load_wrapper returns None or an abject. + Iff it returns None, the object is deemed not-loaded and thus, + self.fn needs to be called to create the object. + It can return None, because self.load returns None or if self.load + expects an open file, but no such file exists. + """ + # Check whether self.load does not expect an open file. + if "file" in load_args or "file" not in self.load_arg_names: + return self.load(**load_args) + + path = load_args["path"] + # If self.load expects an open file but there is none, run self.fn. + if not path.exists(): + return None + + with open(path, "r" if self.plain_text else "rb") as file: + # If specified, the first line is a json of the keyword arguments. + if self.save_args: + file.readline() + return self.load(**{"file": file} | load_args) + + + def save_wrapper(self, obj, path, **save_args): + """Only open a file if the save function requests you to. + """ + Path(path).parent.mkdir(parents=True, exist_ok=True) + if "file" not in self.save_arg_names: + return self.save(obj, **{"path": path} | save_args) + + if "file" in save_args: + file = save_args["file"] + else: + file = open(path, "w" if self.plain_text else "wb") + + if self.save_args: + file.write(self.to_json(**save_args)) + + self.save(obj, **{"file": file, "path": path} | save_args) + + if "file" not in save_args: + file.close() + + def to_json(self, **kwargs) -> bytes: """Serialize all keyword arguments to json. We never serialize positional arguments""" diff --git a/src/warning.py b/src/warning.py index bf9781b..39d3fd4 100644 --- a/src/warning.py +++ b/src/warning.py @@ -14,5 +14,10 @@ class ToDoWarning(Warning): def __str__(self): return repr(self.message) +PAST_TODO_MESSAGES = set() + def todo(msg): - warnings.warn(msg, ToDoWarning) + global PAST_TODO_MESSAGES + if msg not in PAST_TODO_MESSAGES: + warnings.warn(msg, ToDoWarning) + PAST_TODO_MESSAGES.add(msg)