From b4f2963a15657d24b5d336d5aac4198320198219 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 09:58:30 +0000 Subject: [PATCH 01/22] Removed walrus operators --- src/saveable_objects/_saveable_object.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/saveable_objects/_saveable_object.py b/src/saveable_objects/_saveable_object.py index 42cd140..7c5dd63 100644 --- a/src/saveable_objects/_saveable_object.py +++ b/src/saveable_objects/_saveable_object.py @@ -89,7 +89,8 @@ def _save(self, path: str, write_mode: Literal["w", "wb", "a", "ab", "x", "xb"] as `mode` for ``open``. By default ``"wb"``. """ if not os.path.exists(path): - if (dirname := os.path.dirname(path)) != '': + dirname = os.path.dirname(path) + if dirname != '': os.makedirs(dirname, exist_ok=True) with open(path, write_mode) as file: cpkl.dump(self, file, pkl.HIGHEST_PROTOCOL) @@ -343,7 +344,8 @@ def loadif(cls, *args, **kwargs) -> Tuple["SaveableObject", bool]: path = bound_args.arguments["path"] except KeyError: path = None - if instance := cls.tryload(path): + instance = cls.tryload(path) + if instance: return instance, True return cls(*args, **kwargs), False @classmethod From 63c36d6a7389e14a60b55daa58b14ddb2880db2b Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 09:59:22 +0000 Subject: [PATCH 02/22] Simplified imports --- src/saveable_objects/checkpointing/_checkpointing.py | 2 +- src/saveable_objects/checkpointing/_checkpointing.pyi | 2 +- src/saveable_objects/extensions/_extensions.py | 2 +- src/saveable_objects/extensions/_extensions.pyi | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/saveable_objects/checkpointing/_checkpointing.py b/src/saveable_objects/checkpointing/_checkpointing.py index d11a8c1..8e0c1e0 100644 --- a/src/saveable_objects/checkpointing/_checkpointing.py +++ b/src/saveable_objects/checkpointing/_checkpointing.py @@ -1,6 +1,6 @@ from typing import Tuple -from .._saveable_object import SaveableObject +from .. import SaveableObject def failed(load_attempt: SaveableObject | bool | Tuple[SaveableObject, bool]) -> bool: """Determines if a :class:`SaveableObject ` diff --git a/src/saveable_objects/checkpointing/_checkpointing.pyi b/src/saveable_objects/checkpointing/_checkpointing.pyi index 6a5e30b..b745907 100644 --- a/src/saveable_objects/checkpointing/_checkpointing.pyi +++ b/src/saveable_objects/checkpointing/_checkpointing.pyi @@ -1,6 +1,6 @@ from typing import Tuple -from .._saveable_object import SaveableObject +from .. import SaveableObject def failed(load_attempt: SaveableObject | bool | Tuple[SaveableObject, bool]) -> bool: """Determines if a ``SaveableObject`` ``.load()``, ``.tryload()``, diff --git a/src/saveable_objects/extensions/_extensions.py b/src/saveable_objects/extensions/_extensions.py index eb8292d..d59a497 100644 --- a/src/saveable_objects/extensions/_extensions.py +++ b/src/saveable_objects/extensions/_extensions.py @@ -1,6 +1,6 @@ from typing import Optional, TypeVar, Generic -from .._saveable_object import SaveableObject +from .. import SaveableObject from .._meta_class import SaveAfterInitMetaClass T = TypeVar("T") diff --git a/src/saveable_objects/extensions/_extensions.pyi b/src/saveable_objects/extensions/_extensions.pyi index f3b5e77..0a58f35 100644 --- a/src/saveable_objects/extensions/_extensions.pyi +++ b/src/saveable_objects/extensions/_extensions.pyi @@ -1,6 +1,6 @@ from typing import TypeVar, Generic -from .._saveable_object import SaveableObject +from .. import SaveableObject from .._meta_class import SaveAfterInitMetaClass T = TypeVar("T") From 88e0c28de6a7945f91b2e97b9554c0a566b3e53a Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 09:59:47 +0000 Subject: [PATCH 03/22] Removed unsupported typing --- src/saveable_objects/_saveable_object_3_7.py | 407 ++++++++++++++++++ .../checkpointing/_checkpointing_3_7.py | 73 ++++ 2 files changed, 480 insertions(+) create mode 100644 src/saveable_objects/_saveable_object_3_7.py create mode 100644 src/saveable_objects/checkpointing/_checkpointing_3_7.py diff --git a/src/saveable_objects/_saveable_object_3_7.py b/src/saveable_objects/_saveable_object_3_7.py new file mode 100644 index 0000000..283e2b5 --- /dev/null +++ b/src/saveable_objects/_saveable_object_3_7.py @@ -0,0 +1,407 @@ +import os +import inspect +import numpy as np +import pickle as pkl +import cloudpickle as cpkl +from typing import Optional, Union, IO, Tuple + +from ._meta_class import SaveAfterInitMetaClass + +class SaveableObject(metaclass=SaveAfterInitMetaClass): + """A utility class for saving objects to pickles and checkpointing. + """ + def __init__(self, path: Optional[str] = None): + """Initialises an instance of the :class:`SaveableObject`. + If a path is specified the saveable object is automatically saved after + initialisation. + + Parameters + ---------- + path : str, optional + The file :attr:`path` to save the object to. If ``None`` then the + object is not saved. By default ``None``. + + Notes + ----- + If no file extension is provided for `path` then the class name and the + ``.pkl`` extension are appended to the file name. + """ + self.path = path + @property + def path(self) -> Optional[str]: + """The current file path of the object. + + Notes + ----- + On setting the value, if no file extension is provided then the class + name and the ``.pkl`` extension are appended to the file name. + """ + return self._path + @path.setter + def path(self, value: Optional[str]): + """Set the current file path of the object. + + Parameters + ---------- + value : str, optional + The new file path of the object. By default ``None``. + + Notes + ----- + If no file extension is provided then the class name and the ``.pkl`` + extension are appended to the file name. + """ + self._path = self._updatepathroot(value) + @classmethod + def _get_name(cls, path: Optional[str]) -> Optional[str]: + """Returns the file name of the specified path (without the file + extension). + + Parameters + ---------- + path : str, optional + The path to obtain the file name for. + + Returns + ------- + str, optional + The file name (without the file extension). + """ + if path is None: + return None + return os.path.split(os.path.splitext(cls._updatepathroot(path))[0])[-1] + @property + def name(self) -> Optional[str]: + """The file name of the object (without the file extension). Note that + `name` is read only. + """ + return self._get_name(self._path) + + def _save(self, path: str, write_mode = "wb"): + """Saves the object to `path` using `write_mode`. + + Parameters + ---------- + path : str + The path to save the object to. + write_mode : Literal["w", "wb", "a", "ab", "x", "xb"], optional + The mode with which to open the file to write to. These are the same + as `mode` for ``open``. By default ``"wb"``. + """ + if not os.path.exists(path): + dirname = os.path.dirname(path) + if dirname != '': + os.makedirs(dirname, exist_ok=True) + with open(path, write_mode) as file: + cpkl.dump(self, file, pkl.HIGHEST_PROTOCOL) + def _getpath(self, path: Optional[str]) -> str: + """Returns the specified or saved :attr:`path`. + + Parameters + ---------- + path : str, optional + Specified path. + + Returns + ------- + str + Returns the specified or saved :attr:`path`. + + Raises + ------ + ValueError + No save path provided. Raised if no path is saved or specified. + """ + path = path if path is not None or not hasattr(self, 'path') else self.path + if path is None: + raise ValueError("No save path provided.") + path = self._updatepathroot(path) + return path + @classmethod + def _updatepathroot(cls, path: Optional[str]) -> Optional[str]: + """If no file extension is provided then the class name and the ``.pkl`` + extension are appended to the file name. + + Parameters + ---------- + path : str, optional + The file path. + + Returns + ------- + str, optional + The modified file path. + """ + if path is None: + return None + split = os.path.splitext(path) + if len(split[1]) == 0: + file_name = os.path.split(split[0])[-1] + prefix = "_" if len(file_name) != 0 and file_name[-1] != "_" else "" + path += prefix + cls.__name__ + ".pkl" + return path + def save(self, path: Optional[str] = None): + """Pickles the current instance. + + Parameters + ---------- + path : str, optional + The path to pickle the instance to. If ``None`` is specified + then the attribute :attr:`path` is used instead. + By default ``None``. + + Raises + ------ + ValueError + Raised if no path specified either by the parameter `path` or the + attribute :attr:`path`. + + Notes + ----- + If no file extension is provided then the class name and the ``.pkl`` + extension are appended to the file name. + """ + self.path = self._getpath(path) + self._save(self.path) + def update_save(self, path: Optional[str] = None) -> bool: + """Pickles the current instance and retains the saved arguments if + they exist. + + Parameters + ---------- + path : str, optional + The path to pickle the instance to. If ``None`` is specified + then the attribute :attr:`path` is used instead. By default + ``None``. + + Returns + ------- + bool + ``True`` if there was an argument pickle to retain. ``False`` if + there was not an argument pickle to retain. + + Raises + ------ + ValueError + Raised if no path specified. + + Notes + ----- + If no file extension is provided then the class name and the ``.pkl`` + extension are appended to the file name. + """ + self.path = self._getpath(path) + file = open(self.path, "rb") + # Throw away the prior save: + try: + type(self)._load(file) + except: + pass + # Retain the parameters: + try: + params = pkl.load(file) + except EOFError: + # Close the file before writing to it + file.close() + self._save(self.path) + return False + else: + # Close the file before writing to it + file.close() + self._save(self.path) + SaveableObject._save(params, self.path, write_mode="ab") + return True + + @classmethod + def _load(cls, file: IO, new_path: Optional[str] = None, strict_typing: bool = True) -> "SaveableObject": + """Loads an instance from the `file`. + + Parameters + ---------- + file : IO + The file to load the instance from. + new_path : str, optional + The path to replace the previous path with. If ``None`` the `path` + is not replaced. By default ``None``. + strict_typing : bool, optional + If ``True`` then the loaded instance must be an instance of `cls`. + By default ``True``. + + Returns + ------- + `cls` + The loaded instance. + + Raises + ------ + TypeError + If `strict_typing` and the loaded instance is not an instance of + `cls`. + + Notes + ----- + ``strict_typing=True`` acts as a safety guard. Setting + ``strict_typing=False`` may increase the probability of unexpected or + uncaught errors. + """ + instance = pkl.load(file) + if strict_typing and not isinstance(instance, cls): + raise TypeError(f"The loaded instance is not an instance of {cls}.") + if new_path is not None: + instance.path = new_path + return instance + @classmethod + def load(cls, path: str, new_path: Optional[str] = None, strict_typing: bool = True) -> "SaveableObject": + """Loads a pickled instance. + + Parameters + ---------- + path : str + The path of the pickle. + new_path : str, optional + The path to replace the previous path with. If ``None`` the `path` + is not replaced. By default ``None``. + strict_typing : bool, optional + If ``True`` then the loaded instance must be an instance of `cls`. + By default ``True``. + + Returns + ------- + SaveableObject + The loaded instance. + + Raises + ------ + TypeError + If `strict_typing` and the loaded instance is not an instance of + `cls`. + + Notes + ----- + ``strict_typing=True`` acts as a safety guard. Setting + ``strict_typing=False`` may increase the probability of unexpected or + uncaught errors. + """ + path = cls._updatepathroot(path) + with open(path, "rb") as file: + return cls._load(file, new_path, strict_typing) + @classmethod + def tryload(cls, path: Optional[str], new_path: Optional[str] = None, strict_typing: bool = True) -> Union["SaveableObject", bool]: + """Attempts to :meth:`load` from the specified `path`. If the loading + fails then ``False`` is returned. + + Parameters + ---------- + path : str, optional + The path of the pickle. If ``None`` then ``False`` is returned. + new_path : str, optional + The path to replace the previous path with. If ``None`` the `path` + is not replaced. By default ``None``. + strict_typing : bool, optional + If ``True`` then the loaded instance must be an instance of `cls`. + By default ``True``. + + Returns + ------- + SaveableObject | Literal[False] + If succeeded the loaded instance, else False. + + Notes + ----- + ``strict_typing=True`` acts as a safety guard. Setting + ``strict_typing=False`` may increase the probability of unexpected or + uncaught errors. + """ + try: + return cls.load(path, new_path, strict_typing) + except (FileNotFoundError, TypeError): + return False + @classmethod + def loadif(cls, *args, **kwargs) -> Tuple["SaveableObject", bool]: + """Attempts to load from a specified `path`. If the loading fails or no + `path` is specified then a new instance of the object is generated with + the specified `*args` and `**kwargs`. + + Parameters + ---------- + *args + The arguments to pass to the initialisation on a failed + :meth:`load`. + path : str, optional + The path of the pickle, by default the parameter is not specified. + **kwargs + The keyword arguments to pass to the initialisation on a failed + :meth:`load`. + + Returns + ------- + (SaveableObject, bool) + The loaded or initialised instance followed by ``True`` if the + instance was loaded and ``False`` if the instance was initialised. + """ + bound_args = inspect.signature(cls.__init__).bind(..., *args, **kwargs) + try: + path = bound_args.arguments["path"] + except KeyError: + path = None + instance = cls.tryload(path) + if instance: + return instance, True + return cls(*args, **kwargs), False + @classmethod + def loadifparams(cls, *args, dependencies: dict = {}, **kwargs) -> Tuple["SaveableObject", bool]: + """Attempts to :meth:`load` from a specified `path`. If the loading + fails or no `path` is specified or the parameters do not match the saved + parameters then a new instance of the object is generated with the + specified `*args` and `**kwargs`. + + Parameters + ---------- + *args + The arguments to pass to the initialisation on a failed + :meth:`load`. + path : str, optional + The path of the pickle, by default the parameter is not specified. + dependencies : dict, optional, must be specified as a keyword argument + A dictionary of additional dependencies to check. + **kwargs + The keyword arguments to pass to the initialisation on a failed + :meth:`load`. + + Returns + ------- + (SaveableObject, bool) + The loaded or initialised instance followed by ``True`` if the + instance was loaded and ``False`` if the instance was initialised. + """ + bound_args = inspect.signature(cls.__init__).bind(..., *args, **kwargs) + try: + path = bound_args.arguments["path"] + except KeyError: + path = None + path = cls._updatepathroot(path) + duplicates = [] + for key in dependencies.keys(): + if key in bound_args.arguments.keys(): + duplicates.append(key) + if len(duplicates) != 0: + raise TypeError(f"The dependencies {duplicates} are also arguments. They must have different names.") + arguments = {**bound_args.arguments, **dependencies} + try: + with open(path, "rb") as file: + instance = cls._load(file) + params = pkl.load(file) + for key, value in arguments.items(): + comparison = params[key] != value + if isinstance(comparison, bool): + if comparison: + raise ValueError + else: + if not np.array_equal(params[key], value): + raise ValueError + return instance, True + except (FileNotFoundError, EOFError, ValueError, TypeError, KeyError): + instance = cls(*args, **kwargs) + if path is not None: + SaveableObject._save(arguments, path, write_mode="ab") + return instance, False \ No newline at end of file diff --git a/src/saveable_objects/checkpointing/_checkpointing_3_7.py b/src/saveable_objects/checkpointing/_checkpointing_3_7.py new file mode 100644 index 0000000..869563a --- /dev/null +++ b/src/saveable_objects/checkpointing/_checkpointing_3_7.py @@ -0,0 +1,73 @@ +from typing import Tuple, Union + +from .. import SaveableObject + +def failed(load_attempt: Union[SaveableObject, bool, Tuple[SaveableObject, bool]]) -> bool: + """Determines if a :class:`SaveableObject ` + :meth:`.load() `, + :meth:`.tryload() `, + :meth:`.loadif() `, or + :meth:`.loadifparams() ` attempt + fails. + + Parameters + ---------- + load_attempt : SaveableObject | bool | (SaveableObject, bool) + The output of :meth:`load() `, + :meth:`tryload() `, + :meth:`loadif() `, or + :meth:`loadifparams() `. + + Returns + ------- + bool + Returns ``True`` is the the `load_attempt` failed, else ``False``. + + Notes + ----- + Example use: + + .. code-block:: python + + if failed(obj := SaveableObject.loadif(*args, path="filename.pkl", **kwargs)): + ... # code that generates obj + ... # code that uses obj + """ + try: + return not load_attempt[1] + except: + return not load_attempt + +def succeeded(load_attempt: Union[SaveableObject, bool, Tuple[SaveableObject, bool]]) -> bool: + """Determines if a :class:`SaveableObject ` + :meth:`.load() `, + :meth:`.tryload() `, + :meth:`.loadif() `, or + :meth:`.loadifparams() ` attempt + succeeds. + + Parameters + ---------- + load_attempt : SaveableObject | bool | (SaveableObject, bool) + The output of :meth:`load() `, + :meth:`tryload() `, + :meth:`loadif() `, or + :meth:`loadifparams() `. + + Returns + ------- + bool + Returns ``True`` is the the `load_attempt` succeeded, else ``False``. + + Notes + ----- + Example use: + + .. code-block:: python + + if succeeded(obj := SaveableObject.loadif(*args, path="filename.pkl", **kwargs)): + ... # code that uses a successfully loaded obj + else: + ... # code to run if obj failed to load + """ + return not failed(load_attempt) \ No newline at end of file From a8258a28509e57370d2dfb092dbf5b3fc6ee8d91 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:01:23 +0000 Subject: [PATCH 04/22] Added version specific imports --- src/saveable_objects/__init__.py | 7 ++++++- src/saveable_objects/checkpointing/__init__.py | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/saveable_objects/__init__.py b/src/saveable_objects/__init__.py index 13d3296..bc26b1b 100644 --- a/src/saveable_objects/__init__.py +++ b/src/saveable_objects/__init__.py @@ -2,4 +2,9 @@ A python package for checkpointing, saving, and loading objects. """ -from ._saveable_object import SaveableObject \ No newline at end of file +import sys + +if sys.version_info[1] >= 8: # check if python is version 3.8 or later + from ._saveable_object import SaveableObject +else: # import from the python version 3.7 compatable file + from ._saveable_object_3_7 import SaveableObject \ No newline at end of file diff --git a/src/saveable_objects/checkpointing/__init__.py b/src/saveable_objects/checkpointing/__init__.py index 968b14b..bbc0e4c 100644 --- a/src/saveable_objects/checkpointing/__init__.py +++ b/src/saveable_objects/checkpointing/__init__.py @@ -2,4 +2,9 @@ A collection of functions for checkpointing objects. """ -from ._checkpointing import failed, succeeded \ No newline at end of file +import sys + +if sys.version_info[1] >= 8: # check if python is version 3.8 or later + from ._checkpointing import failed, succeeded +else: # import from the python version 3.7 compatable file + from ._checkpointing_3_7 import failed, succeeded \ No newline at end of file From bd62bd1caf8c9ee2fbe8e28e826b7dff095ae707 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:01:55 +0000 Subject: [PATCH 05/22] Reduced the version requirement to 3.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dcba10a..8252356 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "A python package for checkpointing, saving, and loading objects." keywords = ["save", "saving", "saveable", "object", "checkpoint", "checkpointing", "load", "loading"] readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent", From 2cba8a398f10f646a4a1256ef5fe5369f31c0f3a Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:02:10 +0000 Subject: [PATCH 06/22] Updated titles in documentation --- docs/index.rst | 4 ++-- docs/reference/index.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 79c9e96..6c56f32 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,8 +3,8 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -saveable-objects documentation -============================== +saveable-objects +================ .. include:: ../README.md :parser: myst_parser.sphinx_ diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 3c524c5..a876af1 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -1,5 +1,5 @@ API Reference -============================= +============= .. autosummary:: :toctree: _autosummary From 47fcc9d03cbc8519bb72f03a8a164f71abce8d2b Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:03:15 +0000 Subject: [PATCH 07/22] Added requirements to the README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index f0d810e..3f1d54f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ The python package can be installed with pip as follows: pip install saveable-objects ``` +### Requirements: + +Python >= 3.7 + +Packages: + +- [NumPy](https://numpy.org/) +- [cloudpickle](https://github.com/cloudpipe/cloudpickle) + ## Documentation Documentation including worked examples can be found at: [https://saveable-objects.readthedocs.io/](https://saveable-objects.readthedocs.io/). From 439bdee610cfb2e9468e274e4a592d69536ac956 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" <92798672+Christopher-K-Long@users.noreply.github.com> Date: Sat, 22 Mar 2025 10:09:16 +0000 Subject: [PATCH 08/22] Test package GitHub workflow now tests on python 3.7 through to 3.13 inclusive. --- .github/workflows/test-python-package.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index a3864ae..a169f9c 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -21,13 +21,16 @@ jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip From 9e6900981e81360650db6da7c26c17cd798476d3 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" <92798672+Christopher-K-Long@users.noreply.github.com> Date: Sat, 22 Mar 2025 10:13:34 +0000 Subject: [PATCH 09/22] Update test-python-package.yml to use 3.7.1 as 3.7.0 is not supported by GitHub test --- .github/workflows/test-python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index a169f9c..bb5741b 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.7.1", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 From 497c5165d4e8ee775652904f7bff2e932e2099b8 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" <92798672+Christopher-K-Long@users.noreply.github.com> Date: Sat, 22 Mar 2025 10:17:29 +0000 Subject: [PATCH 10/22] Changed version of Ubuntu for GitHub Tests as python 3.7 has not been compiled on latest Ubuntu --- .github/workflows/test-python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index bb5741b..f5e1016 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -20,10 +20,10 @@ permissions: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.7.1", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 From 680609c8a252688e7cace28b980f860c7b477378 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:19:33 +0000 Subject: [PATCH 11/22] Corrected version specific imports --- src/saveable_objects/__init__.py | 2 +- src/saveable_objects/checkpointing/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/saveable_objects/__init__.py b/src/saveable_objects/__init__.py index bc26b1b..91dac11 100644 --- a/src/saveable_objects/__init__.py +++ b/src/saveable_objects/__init__.py @@ -4,7 +4,7 @@ import sys -if sys.version_info[1] >= 8: # check if python is version 3.8 or later +if sys.version_info[1] >= 10: # check if python is version 3.8 or later from ._saveable_object import SaveableObject else: # import from the python version 3.7 compatable file from ._saveable_object_3_7 import SaveableObject \ No newline at end of file diff --git a/src/saveable_objects/checkpointing/__init__.py b/src/saveable_objects/checkpointing/__init__.py index bbc0e4c..4bd144d 100644 --- a/src/saveable_objects/checkpointing/__init__.py +++ b/src/saveable_objects/checkpointing/__init__.py @@ -4,7 +4,7 @@ import sys -if sys.version_info[1] >= 8: # check if python is version 3.8 or later +if sys.version_info[1] >= 10: # check if python is version 3.8 or later from ._checkpointing import failed, succeeded else: # import from the python version 3.7 compatable file from ._checkpointing_3_7 import failed, succeeded \ No newline at end of file From 7ca2ce6be8d0c378cac7b22ed9acb8cf6c7b7fb8 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:24:05 +0000 Subject: [PATCH 12/22] Moved package building to latest python version --- .github/workflows/test-python-package.yml | 31 ++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index f5e1016..2cf8736 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -19,7 +19,36 @@ permissions: jobs: build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + test: + name: >- + Testing distribution + needs: + - build runs-on: ubuntu-20.04 strategy: matrix: @@ -36,7 +65,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - python -m pip install -e ./ + python -m pip install -e ./dist/*.whl - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From 7e3b6748e4525387b646750338b21cc0d5439a16 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:27:50 +0000 Subject: [PATCH 13/22] Updated .whl glob --- .github/workflows/test-python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index 2cf8736..d328391 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -65,7 +65,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - python -m pip install -e ./dist/*.whl + for %%w in (./dist/*.whl) do python -m pip install -e %%w - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From a7e6830448a306ce8dc89dbbaea32860f0079d98 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:48:29 +0000 Subject: [PATCH 14/22] Bug fix in for loop --- .github/workflows/test-python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index d328391..470b09e 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -65,7 +65,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - for %%w in (./dist/*.whl) do python -m pip install -e %%w + for w in ./dist/*.whl; do python -m pip install $w; done - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From bd961de0d92acf69f35d864b67be8e1c0cfe7d6b Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:52:49 +0000 Subject: [PATCH 15/22] bug fix (downloading the builds before installing) --- .github/workflows/test-python-package.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index 470b09e..207b6a5 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -55,6 +55,11 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -65,7 +70,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - for w in ./dist/*.whl; do python -m pip install $w; done + for w in dist/*.whl; do python -m pip install $w; done - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From 19dc44a450a19a78def5c3ab0d95ae7b4d1a1460 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 10:55:33 +0000 Subject: [PATCH 16/22] tyring a different directory pre-fix --- .github/workflows/test-python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index 207b6a5..63b765f 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -70,7 +70,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - for w in dist/*.whl; do python -m pip install $w; done + for w in ./dist/*.whl; do python -m pip install $w; done - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From 528bda55be4ab5d632a0737ff783f16318e04f0d Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 11:01:07 +0000 Subject: [PATCH 17/22] picking correct shell --- .github/workflows/test-python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index 63b765f..4984893 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -71,6 +71,7 @@ jobs: pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi for w in ./dist/*.whl; do python -m pip install $w; done + shell: bash - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From d7e2d329b00f0f06b0cfc1a549b2e579ae8af8eb Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 11:05:25 +0000 Subject: [PATCH 18/22] without for loop --- .github/workflows/test-python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index 4984893..bbbbec0 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -70,8 +70,8 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - for w in ./dist/*.whl; do python -m pip install $w; done - shell: bash + mv ./dist/*.whl ./dist/package.whl + python -m pip install ./dist/package.whl - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From 99f9d657caadc5ea3078de238251d91f80cb569e Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 11:08:07 +0000 Subject: [PATCH 19/22] printing files --- .github/workflows/test-python-package.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index bbbbec0..7cd5c9b 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -70,6 +70,8 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + ls + ls ./dist mv ./dist/*.whl ./dist/package.whl python -m pip install ./dist/package.whl - name: Lint with flake8 From f22d8c552d3041ea2bf1c97b832d1d272d0c6225 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 11:17:50 +0000 Subject: [PATCH 20/22] bug fix --- .github/workflows/test-python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index 7cd5c9b..d00ffb2 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -55,12 +55,12 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: + - uses: actions/checkout@v4 - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: From 83f5e453a4a124e885063026dfb88a65f58d672f Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 11:19:47 +0000 Subject: [PATCH 21/22] returning to for loop strucutre now the dists are actually being downloaded --- .github/workflows/test-python-package.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test-python-package.yml b/.github/workflows/test-python-package.yml index d00ffb2..3397627 100644 --- a/.github/workflows/test-python-package.yml +++ b/.github/workflows/test-python-package.yml @@ -70,10 +70,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - ls - ls ./dist - mv ./dist/*.whl ./dist/package.whl - python -m pip install ./dist/package.whl + for w in ./dist/*.whl; do python -m pip install $w; done - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names From 69a97ea8f47ea4ebf541be96b8828993f0778633 Mon Sep 17 00:00:00 2001 From: "Christopher K. Long" Date: Sat, 22 Mar 2025 11:23:50 +0000 Subject: [PATCH 22/22] Version 1.1.0 --- ChangeLog.md | 8 +++++++- README.md | 2 +- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 957cbd5..4656fe3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,12 @@ # [saveable-objects](README.md) Change Log -## Release 1.0.0 +## Release v1.1.0 + +This release adds support for earlier python versions that 3.10. Specifically the changes in this release are: +- Support was added for python versions 3.7, 3.8, and 3.9 +- Minor updates to the documentation. + +## Release v1.0.0 This is the initial release. Future changes to this release will be documented above. \ No newline at end of file diff --git a/README.md b/README.md index 3f1d54f..10ee730 100644 --- a/README.md +++ b/README.md @@ -28,5 +28,5 @@ Source code can be found at: [https://github.com/Christopher-K-Long/saveable-obj ## Version and Changes -The current version is [`1.0.0`](ChangeLog.md#release-100). Please see the [Change Log](ChangeLog.md) for more +The current version is [`1.1.0`](ChangeLog.md#release-110). Please see the [Change Log](ChangeLog.md) for more details. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8252356..4bace66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "saveable-objects" -version = "1.0.0" +version = "1.1.0" authors = [ { name="Christopher_K._Long", email="ckl45@cam.ac.uk" }, ]