path-tools-med 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ __version__ = "0.0.1"
2
+
3
+ from .tools import (
4
+ PathRerooter,
5
+ backup_path,
6
+ get_highest_backup_version,
7
+ make_versioned_backup,
8
+ )
9
+
10
+ __all__ = [
11
+ "PathRerooter",
12
+ "backup_path",
13
+ "get_highest_backup_version",
14
+ "make_versioned_backup",
15
+ ]
File without changes
@@ -0,0 +1,142 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ def backup_path(path: Path | str, backup_extension: str = ".bak") -> Path:
9
+ """
10
+ Rename the path object specified by `path` to `path.backup_extension`.
11
+ If there is an existing `path.backup_extension`, file it will be renamed
12
+ with a version number extension using `make_versioned_backup`.
13
+
14
+ WARNING: The operations are not atomic and so it is possible for another
15
+ process to create another `path.backup_extension` or `path.backup_extension.~#~`
16
+ file in between the time that this process determines the file to create doesn't
17
+ exist and when it renames the file. If this happens the earlier created file
18
+ will be overwritten, see `pathlib.Path.replace`.
19
+
20
+ :param path: the path to the file to be renamed
21
+ :param backup_extension: the backup extension to be appended
22
+ :return: the new path object
23
+ """
24
+ this_path = Path(path)
25
+ path_backup = Path(str(this_path) + backup_extension)
26
+ if path_backup.exists():
27
+ make_versioned_backup(path_backup)
28
+ return this_path.replace(path_backup)
29
+
30
+
31
+ def make_versioned_backup(path: Path) -> Path:
32
+ """
33
+ Rename the path object specified with a version number extension
34
+ that will be greater than any existing version number.
35
+
36
+ WARNING: The operations are not atomic and so it is possible for another
37
+ process to create another `path.backup_extension` or `path.backup_extension.~#~`
38
+ file in between the time that this process determines the file to create doesn't
39
+ exist and when it renames the file. If this happens the earlier created file
40
+ will be overwritten, see `pathlib.Path.replace`.
41
+
42
+ :param path: the path to the file to be renamed
43
+ :return: the new path object
44
+ """
45
+ this_path = Path(path)
46
+ return this_path.replace(
47
+ f"{this_path}.~{get_highest_backup_version(this_path) + 1}~",
48
+ )
49
+
50
+
51
+ def get_highest_backup_version(path: Path | str) -> int:
52
+ """
53
+ Return the highest existing backup version number for existing `path.~#~` files.
54
+ If no such files exist return 0.
55
+
56
+ :param path:
57
+ :return: hightest existing backup version number
58
+ """
59
+ versions = [0]
60
+ this_path = Path(path)
61
+ versions.extend(
62
+ [
63
+ int(x[x.rindex("~", 0, -1) + 1 : -1])
64
+ for x in map(str, this_path.parent.glob(f"{this_path.name}.~*~"))
65
+ ],
66
+ )
67
+ return max(versions)
68
+
69
+
70
+ class PathRerooter:
71
+ """Class to facilitate converting paths with old-root to new-root.
72
+
73
+ This is not a replacement for `shutil.copytree`. This does not actually rename
74
+ any files. It just creates the updated path, and so is useful for updating data
75
+ files containing paths that have been changed.
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ old_path_root: Path | str | None,
81
+ new_path_root: Path | str | None,
82
+ *_: Any,
83
+ ignore: bool = False,
84
+ ):
85
+ """
86
+ Constructor
87
+
88
+ If both roots are None, then the fix operation quietly does nothing.
89
+
90
+ If ignore is true, then the fix operation quietly does nothing for
91
+ paths that do not start with the old root.
92
+ This applies to all invocations of `fix(path)` called on this
93
+ `PathRerooter` object.
94
+ To only selectively ignore these situations leave `ignore=False`
95
+ in the constructor,
96
+ but use `fix(path, ignore=True)` on the pertinant invocations.
97
+
98
+ :param old_path_root: the old root path to be converted from
99
+ :param new_path_root: the new root path to be converted to
100
+ :param ignore: if true, quietly just return paths that do not
101
+ start with the old root.
102
+ """
103
+ self.noop = old_path_root is None and new_path_root is None
104
+ self.old_path_root_parts = list(Path(old_path_root).parts) if old_path_root else []
105
+ self.new_path_root_parts = list(Path(new_path_root).parts) if new_path_root else []
106
+ self.ignore = ignore
107
+
108
+ def fix(self, path: Path | str, *_: Any, ignore: bool = False) -> Path:
109
+ """
110
+ Apply the defined root conversion to the specified path returning
111
+ the new path object.
112
+
113
+ No files are renamed, the new `pathlib.Path` object is just created.
114
+
115
+ A RuntimeError is raised if the path does not start with the old-path,
116
+ unless either `ignore=True` is passed on this invocation,
117
+ or `ignore=True` was set in the constructor.
118
+
119
+ :param path: path to be converted
120
+ :param ignore: if true, quietly just return paths that do not start
121
+ with the old root.
122
+ :return: the converted path
123
+ """
124
+ this_path = Path(path)
125
+ if self.noop:
126
+ return this_path
127
+
128
+ old_path_parts = list(this_path.parts)
129
+ for part in self.old_path_root_parts:
130
+ if part == old_path_parts[0]:
131
+ old_path_parts.pop(0)
132
+ elif ignore or self.ignore:
133
+ return this_path
134
+ else:
135
+ msg = (
136
+ f"old_path_part '{part}' does not match "
137
+ f"'{old_path_parts[0]}' in path '{this_path}'"
138
+ )
139
+ logger.error(msg)
140
+ raise RuntimeError(msg)
141
+ new_path_parts = [*self.new_path_root_parts, *old_path_parts]
142
+ return Path().joinpath(*new_path_parts)
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: path-tools-med
3
+ Version: 0.1.0
4
+ Summary: A few path related functions I wish pathlib supported.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Author: Malcolm E. Davis
8
+ Author-email: malcolm3davis@gmail.com
9
+ Requires-Python: >=3.10
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Project-URL: Documentation, https://malcolm3davis.github.io/path-tools-med
23
+ Project-URL: Homepage, https://malcolm3davis.github.io/path-tools-med
24
+ Project-URL: Repository, https://github.com/malcolm3davis/path-tools-med
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Malcolm's Path Tools
28
+
29
+ [![PyPI](https://img.shields.io/pypi/v/path-tools-med?style=flat-square)](https://pypi.python.org/pypi/path-tools-med/)
30
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/path-tools-med?style=flat-square)](https://pypi.python.org/pypi/path-tools-med/)
31
+ [![PyPI - License](https://img.shields.io/pypi/l/path-tools-med?style=flat-square)](https://pypi.python.org/pypi/path-tools-med/)
32
+ [![Coookiecutter - Malcolm](https://img.shields.io/badge/cookiecutter-Malcolm_Wolt-00c2e8?style=flat-square&logo=cookiecutter&logoColor=D4AA00&link=https://github.com/malcolm-3/malcolm-3-python-package-cookiecutter)](https://github.com/woltapp/wolt-python-package-cookiecutter)
33
+
34
+
35
+ ---
36
+
37
+ **Documentation**: [https://malcolm3davis.github.io/path-tools-med](https://malcolm3davis.github.io/path-tools-med)
38
+
39
+ **Source Code**: [https://github.com/malcolm3davis/path-tools-med](https://github.com/malcolm3davis/path-tools-med)
40
+
41
+ **PyPI**: [https://pypi.org/project/path-tools-med/](https://pypi.org/project/path-tools-med/)
42
+
43
+ ---
44
+
45
+ A few path related functions I wish pathlib supported.
46
+
47
+ ## Usage
48
+
49
+ - ``make_versioned_backup(path: Path|str) -> Path``
50
+ - Renames the specified path adding a ``.~#~`` style version number that is higher than any existing version number.
51
+ - ``backup_path(paht: Path|str, backup_extension=".bak") -> Path``
52
+ - Renames the specified file adding the backup_extension.
53
+ If that backup file already exists it renamed with ``make_versioned_backup``.
54
+ - ``get_highest_backup_version(path: Path|str) -> int``
55
+ - Utility function to see what the current highest backup version number exists.
56
+
57
+ It also provides the ``PathRerooter`` class that can be used to update path objects to have
58
+ a new initial root path.
59
+
60
+ ```python
61
+ r = PathRerooter("/old/root/path", "/new/root/path/is/here")
62
+ assert r.fix("/old/root/path/some/file.txt") == "/new/root/path/is/here/some/file.txt"
63
+ ```
64
+
65
+ ## Installation
66
+
67
+ ```sh
68
+ pip install path-tools-med
69
+ ```
70
+
71
+ ## Development
72
+
73
+ * Clone this repository
74
+ * Requirements:
75
+ * [Poetry](https://python-poetry.org/)
76
+ * Python 3.8+
77
+ * Create a virtual environment and install the dependencies
78
+
79
+ ```sh
80
+ poetry install
81
+ ```
82
+
83
+ * Activate the virtual environment
84
+
85
+ ```sh
86
+ poetry shell
87
+ ```
88
+
89
+ ### Testing
90
+
91
+ ```sh
92
+ pytest
93
+ ```
94
+
95
+ ### Documentation
96
+
97
+ The documentation is automatically generated from the content of the [docs directory](https://github.com/malcolm3davis/path-tools-med/tree/master/docs) and from the docstrings
98
+ of the public signatures of the source code. The documentation is updated and published as a [Github Pages page](https://pages.github.com/) automatically as part each release.
99
+
100
+ ### Releasing
101
+
102
+ Trigger the [Draft release workflow](https://github.com/malcolm3davis/path-tools-med/actions/workflows/draft_release.yml)
103
+ (press _Run workflow_). This will update the changelog & version and create a GitHub release which is in _Draft_ state.
104
+
105
+ Find the draft release from the
106
+ [GitHub releases](https://github.com/malcolm3davis/path-tools-med/releases) and publish it. When
107
+ a release is published, it'll trigger [release](https://github.com/malcolm3davis/path-tools-med/blob/master/.github/workflows/release.yml) workflow which creates PyPI
108
+ release and deploys updated documentation.
109
+
110
+ ### Pre-commit
111
+
112
+ Pre-commit hooks run all the auto-formatting (`ruff format`), linters (e.g. `ruff` and `mypy`), and other quality
113
+ checks to make sure the changeset is in good shape before a commit/push happens.
114
+
115
+ You can install the hooks with (runs for each commit):
116
+
117
+ ```sh
118
+ pre-commit install
119
+ ```
120
+
121
+ Or if you want them to run only for each push:
122
+
123
+ ```sh
124
+ pre-commit install -t pre-push
125
+ ```
126
+
127
+ Or if you want e.g. want to run all checks manually for all files:
128
+
129
+ ```sh
130
+ pre-commit run --all-files
131
+ ```
132
+
133
+ ---
134
+
135
+ This project was generated using the [wolt-python-package-cookiecutter](https://github.com/woltapp/wolt-python-package-cookiecutter) template.
136
+
@@ -0,0 +1,7 @@
1
+ path_tools_med/__init__.py,sha256=uW5wo65sVt_IZYJgBw76wDlMJmvWDJeQlXoURWYQrpE,257
2
+ path_tools_med/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ path_tools_med/tools.py,sha256=ji-u3Th-GMcH_8vAJdVw22wfMYZp6UwoY63ER2nh4Kc,5395
4
+ path_tools_med-0.1.0.dist-info/METADATA,sha256=H0es4XRs2JS1etIVTpeYNDjhVj5JNCBAp13rAxymcu4,4965
5
+ path_tools_med-0.1.0.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
6
+ path_tools_med-0.1.0.dist-info/licenses/LICENSE,sha256=rELAJLOKMmhrGVEGrfHFl_8KpjgFqbKIb3NLe7Esz1g,1109
7
+ path_tools_med-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.4.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Malcolm E. Davis <malcolm3davis@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.