path-tools-med 0.1.0__tar.gz
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.
- path_tools_med-0.1.0/LICENSE +20 -0
- path_tools_med-0.1.0/PKG-INFO +136 -0
- path_tools_med-0.1.0/README.md +109 -0
- path_tools_med-0.1.0/pyproject.toml +135 -0
- path_tools_med-0.1.0/src/path_tools_med/__init__.py +15 -0
- path_tools_med-0.1.0/src/path_tools_med/py.typed +0 -0
- path_tools_med-0.1.0/src/path_tools_med/tools.py +142 -0
|
@@ -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.
|
|
@@ -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
|
+
[](https://pypi.python.org/pypi/path-tools-med/)
|
|
30
|
+
[](https://pypi.python.org/pypi/path-tools-med/)
|
|
31
|
+
[](https://pypi.python.org/pypi/path-tools-med/)
|
|
32
|
+
[](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,109 @@
|
|
|
1
|
+
# Malcolm's Path Tools
|
|
2
|
+
|
|
3
|
+
[](https://pypi.python.org/pypi/path-tools-med/)
|
|
4
|
+
[](https://pypi.python.org/pypi/path-tools-med/)
|
|
5
|
+
[](https://pypi.python.org/pypi/path-tools-med/)
|
|
6
|
+
[](https://github.com/woltapp/wolt-python-package-cookiecutter)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Documentation**: [https://malcolm3davis.github.io/path-tools-med](https://malcolm3davis.github.io/path-tools-med)
|
|
12
|
+
|
|
13
|
+
**Source Code**: [https://github.com/malcolm3davis/path-tools-med](https://github.com/malcolm3davis/path-tools-med)
|
|
14
|
+
|
|
15
|
+
**PyPI**: [https://pypi.org/project/path-tools-med/](https://pypi.org/project/path-tools-med/)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
A few path related functions I wish pathlib supported.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
- ``make_versioned_backup(path: Path|str) -> Path``
|
|
24
|
+
- Renames the specified path adding a ``.~#~`` style version number that is higher than any existing version number.
|
|
25
|
+
- ``backup_path(paht: Path|str, backup_extension=".bak") -> Path``
|
|
26
|
+
- Renames the specified file adding the backup_extension.
|
|
27
|
+
If that backup file already exists it renamed with ``make_versioned_backup``.
|
|
28
|
+
- ``get_highest_backup_version(path: Path|str) -> int``
|
|
29
|
+
- Utility function to see what the current highest backup version number exists.
|
|
30
|
+
|
|
31
|
+
It also provides the ``PathRerooter`` class that can be used to update path objects to have
|
|
32
|
+
a new initial root path.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
r = PathRerooter("/old/root/path", "/new/root/path/is/here")
|
|
36
|
+
assert r.fix("/old/root/path/some/file.txt") == "/new/root/path/is/here/some/file.txt"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pip install path-tools-med
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
* Clone this repository
|
|
48
|
+
* Requirements:
|
|
49
|
+
* [Poetry](https://python-poetry.org/)
|
|
50
|
+
* Python 3.8+
|
|
51
|
+
* Create a virtual environment and install the dependencies
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
poetry install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
* Activate the virtual environment
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
poetry shell
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Testing
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
pytest
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Documentation
|
|
70
|
+
|
|
71
|
+
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
|
|
72
|
+
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.
|
|
73
|
+
|
|
74
|
+
### Releasing
|
|
75
|
+
|
|
76
|
+
Trigger the [Draft release workflow](https://github.com/malcolm3davis/path-tools-med/actions/workflows/draft_release.yml)
|
|
77
|
+
(press _Run workflow_). This will update the changelog & version and create a GitHub release which is in _Draft_ state.
|
|
78
|
+
|
|
79
|
+
Find the draft release from the
|
|
80
|
+
[GitHub releases](https://github.com/malcolm3davis/path-tools-med/releases) and publish it. When
|
|
81
|
+
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
|
|
82
|
+
release and deploys updated documentation.
|
|
83
|
+
|
|
84
|
+
### Pre-commit
|
|
85
|
+
|
|
86
|
+
Pre-commit hooks run all the auto-formatting (`ruff format`), linters (e.g. `ruff` and `mypy`), and other quality
|
|
87
|
+
checks to make sure the changeset is in good shape before a commit/push happens.
|
|
88
|
+
|
|
89
|
+
You can install the hooks with (runs for each commit):
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
pre-commit install
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or if you want them to run only for each push:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
pre-commit install -t pre-push
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or if you want e.g. want to run all checks manually for all files:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
pre-commit run --all-files
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
This project was generated using the [wolt-python-package-cookiecutter](https://github.com/woltapp/wolt-python-package-cookiecutter) template.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "path-tools-med"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A few path related functions I wish pathlib supported."
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Malcolm E. Davis", email = "malcolm3davis@gmail.com"},
|
|
7
|
+
]
|
|
8
|
+
license = "MIT"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Documentation = "https://malcolm3davis.github.io/path-tools-med"
|
|
28
|
+
Homepage = "https://malcolm3davis.github.io/path-tools-med"
|
|
29
|
+
Repository = "https://github.com/malcolm3davis/path-tools-med"
|
|
30
|
+
|
|
31
|
+
[tool.poetry]
|
|
32
|
+
packages = [
|
|
33
|
+
{ include = "path_tools_med", from = "src" }
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.poetry.dependencies]
|
|
37
|
+
python = ">=3.10.1, <4.0"
|
|
38
|
+
|
|
39
|
+
[tool.poetry.group.dev.dependencies]
|
|
40
|
+
mkdocstrings = {version = ">=0.23", extras = ["python"]}
|
|
41
|
+
mkdocs-material = "*"
|
|
42
|
+
mypy = "*"
|
|
43
|
+
pre-commit = "*"
|
|
44
|
+
pymdown-extensions = "*"
|
|
45
|
+
pytest = "*"
|
|
46
|
+
pytest-github-actions-annotate-failures = "*"
|
|
47
|
+
pytest-cov = "*"
|
|
48
|
+
python-kacl = "*"
|
|
49
|
+
ruff = ">=0.2.0"
|
|
50
|
+
poetry-bumpversion = "^0.3.3"
|
|
51
|
+
|
|
52
|
+
[build-system]
|
|
53
|
+
requires = ["poetry-core>=2.0.0"]
|
|
54
|
+
build-backend = "poetry.core.masonry.api"
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
target-version = "py310" # The lowest supported version
|
|
58
|
+
exclude = ["tests/"]
|
|
59
|
+
line-length = 100
|
|
60
|
+
|
|
61
|
+
[tool.ruff.lint]
|
|
62
|
+
# By default, enable all the lint rules.
|
|
63
|
+
# Add to the ignore list below if you don't want some rules.
|
|
64
|
+
# If you need some ignores for certain modules, see tool.ruff.lint.per-file-ignores below.
|
|
65
|
+
# For individual ignore cases, prefer inline `# noqa`s within the code.
|
|
66
|
+
select = ["ALL"]
|
|
67
|
+
ignore = [
|
|
68
|
+
"ANN", # Type hints related, let mypy handle these.
|
|
69
|
+
"D", # Docstrings related, way too strict to our taste
|
|
70
|
+
"COM812", # missing-trailing-comma
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint.per-file-ignores]
|
|
74
|
+
"tests/**" = [
|
|
75
|
+
"S101", # "Use of `assert` detected"
|
|
76
|
+
"ARG", # "Unused function argument". Fixtures are often unused.
|
|
77
|
+
"S105", # "Possible hardcoded password".
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.mccabe]
|
|
81
|
+
max-complexity = 10
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint.pep8-naming]
|
|
84
|
+
classmethod-decorators = [
|
|
85
|
+
"classmethod",
|
|
86
|
+
"pydantic.validator",
|
|
87
|
+
"pydantic.root_validator",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
addopts = """\
|
|
92
|
+
--cov path_tools_med \
|
|
93
|
+
--cov tests \
|
|
94
|
+
--cov-report term-missing \
|
|
95
|
+
--no-cov-on-fail \
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
[tool.coverage.report]
|
|
99
|
+
fail_under = 100
|
|
100
|
+
exclude_lines = [
|
|
101
|
+
'if TYPE_CHECKING:',
|
|
102
|
+
'pragma: no cover'
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[tool.mypy]
|
|
106
|
+
# This is the global mypy configuration.
|
|
107
|
+
# Avoid changing this!
|
|
108
|
+
strict = true # See all the enabled flags `mypy --help | grep -A 10 'Strict mode'`
|
|
109
|
+
disallow_any_unimported = true
|
|
110
|
+
exclude = [
|
|
111
|
+
'venv/',
|
|
112
|
+
'tests/'
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
[tool.poetry_bumpversion.file."src/path_tools_med/__init__.py"]
|
|
116
|
+
|
|
117
|
+
[tool.poetry_bumpversion.file."mkdocs.yml"]
|
|
118
|
+
search = "site_name: \"Malcolm's Path Tools v{current_version}\""
|
|
119
|
+
replace = "site_name: \"Malcolm's Path Tools v{new_version}\""
|
|
120
|
+
|
|
121
|
+
# If you need to ignore something for some specific module,
|
|
122
|
+
# add overrides for them. Avoid changing the global config!
|
|
123
|
+
# For example:
|
|
124
|
+
# [[tool.mypy.overrides]]
|
|
125
|
+
# module = [
|
|
126
|
+
# "my_unpyted_dependency1.*",
|
|
127
|
+
# "my_unpyted_dependency2.*"
|
|
128
|
+
# ]
|
|
129
|
+
# ignore_missing_imports = true
|
|
130
|
+
|
|
131
|
+
# [[tool.mypy.overrides]]
|
|
132
|
+
# module = [
|
|
133
|
+
# "tests/my_thing/test_my_thing",
|
|
134
|
+
# ]
|
|
135
|
+
# disallow_untyped_defs = false
|
|
@@ -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)
|