edq-utils 0.0.1__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.
Potentially problematic release.
This version of edq-utils might be problematic. Click here for more details.
- edq_utils-0.0.1/LICENSE +21 -0
- edq_utils-0.0.1/PKG-INFO +61 -0
- edq_utils-0.0.1/README.md +18 -0
- edq_utils-0.0.1/edq/__init__.py +1 -0
- edq_utils-0.0.1/edq/util/__init__.py +0 -0
- edq_utils-0.0.1/edq/util/dirent.py +154 -0
- edq_utils-0.0.1/edq/util/dirent_test.py +170 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/a.txt +1 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/dir_1/b.txt +1 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/dir_1/dir_2/c.txt +1 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/file_empty +0 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/symlinklink_a.txt +1 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/symlinklink_dir_1/b.txt +1 -0
- edq_utils-0.0.1/edq/util/testdata/dirent-operations/symlinklink_dir_1/dir_2/c.txt +1 -0
- edq_utils-0.0.1/edq_utils.egg-info/PKG-INFO +61 -0
- edq_utils-0.0.1/edq_utils.egg-info/SOURCES.txt +21 -0
- edq_utils-0.0.1/edq_utils.egg-info/dependency_links.txt +1 -0
- edq_utils-0.0.1/edq_utils.egg-info/requires.txt +6 -0
- edq_utils-0.0.1/edq_utils.egg-info/top_level.txt +1 -0
- edq_utils-0.0.1/pyproject.toml +51 -0
- edq_utils-0.0.1/requirements-dev.txt +4 -0
- edq_utils-0.0.1/requirements.txt +0 -0
- edq_utils-0.0.1/setup.cfg +4 -0
edq_utils-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 EduLinq
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, 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,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
edq_utils-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edq-utils
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Common utilities used by EduLinq Python projects.
|
|
5
|
+
Author-email: Eriq Augustine <eriq@edulinq.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 EduLinq
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/edulinq/python-utils
|
|
29
|
+
Project-URL: Repository, https://github.com/edulinq/python-utils
|
|
30
|
+
Keywords: education,utilities
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
34
|
+
Requires-Python: >=3.8
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: mypy>=1.14.1; extra == "dev"
|
|
39
|
+
Requires-Dist: pylint; extra == "dev"
|
|
40
|
+
Requires-Dist: twine; extra == "dev"
|
|
41
|
+
Requires-Dist: vermin; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# EduLinq Python Utilities
|
|
45
|
+
|
|
46
|
+
Common utilities used by EduLinq Python projects.
|
|
47
|
+
|
|
48
|
+
## Installation / Requirements
|
|
49
|
+
|
|
50
|
+
This project requires [Python](https://www.python.org/) >= 3.8.
|
|
51
|
+
|
|
52
|
+
The project can be installed from PyPi with:
|
|
53
|
+
```
|
|
54
|
+
pip3 install edq-utils
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Standard Python requirements are listed in `pyproject.toml`.
|
|
58
|
+
The project and Python dependencies can be installed from source with:
|
|
59
|
+
```
|
|
60
|
+
pip3 install .
|
|
61
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# EduLinq Python Utilities
|
|
2
|
+
|
|
3
|
+
Common utilities used by EduLinq Python projects.
|
|
4
|
+
|
|
5
|
+
## Installation / Requirements
|
|
6
|
+
|
|
7
|
+
This project requires [Python](https://www.python.org/) >= 3.8.
|
|
8
|
+
|
|
9
|
+
The project can be installed from PyPi with:
|
|
10
|
+
```
|
|
11
|
+
pip3 install edq-utils
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Standard Python requirements are listed in `pyproject.toml`.
|
|
15
|
+
The project and Python dependencies can be installed from source with:
|
|
16
|
+
```
|
|
17
|
+
pip3 install .
|
|
18
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.0.1'
|
|
File without changes
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
DEAULT_ENCODING: str = 'utf-8'
|
|
8
|
+
""" The default encoding that will be used when reading and writing. """
|
|
9
|
+
|
|
10
|
+
def get_temp_path(prefix: str = '', suffix: str = '', rm: bool = True) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Get a path to a valid (but not currently existing) temp dirent.
|
|
13
|
+
If rm is True, then the dirent will be attempted to be deleted on exit
|
|
14
|
+
(no error will occur if the path is not there).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
path = None
|
|
18
|
+
while ((path is None) or os.path.exists(path)):
|
|
19
|
+
path = os.path.join(tempfile.gettempdir(), prefix + str(uuid.uuid4()) + suffix)
|
|
20
|
+
|
|
21
|
+
if (rm):
|
|
22
|
+
atexit.register(remove, path)
|
|
23
|
+
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
def get_temp_dir(prefix: str = '', suffix: str = '', rm: bool = True) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Get a temp directory.
|
|
29
|
+
The directory will exist when returned.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
path = get_temp_path(prefix = prefix, suffix = suffix, rm = rm)
|
|
33
|
+
mkdir(path)
|
|
34
|
+
return path
|
|
35
|
+
|
|
36
|
+
def mkdir(path: str) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Make a directory (including any required parent directories).
|
|
39
|
+
Does not complain if the directory (or parents) already exist.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
os.makedirs(path, exist_ok = True)
|
|
43
|
+
|
|
44
|
+
def remove(path: str) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Remove the given path.
|
|
47
|
+
The path can be of any type (dir, file, link),
|
|
48
|
+
and does not need to exist.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
if (not os.path.exists(path)):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if (os.path.isfile(path) or os.path.islink(path)):
|
|
55
|
+
os.remove(path)
|
|
56
|
+
elif (os.path.isdir(path)):
|
|
57
|
+
shutil.rmtree(path)
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"Unknown type of dirent: '{path}'.")
|
|
60
|
+
|
|
61
|
+
def move(source: str, dest: str) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Move the source dirent to the given destination.
|
|
64
|
+
Any existing destination will be removed before moving.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# If dest is a dir, then resolve the path.
|
|
68
|
+
if (os.path.isdir(dest)):
|
|
69
|
+
dest = os.path.abspath(os.path.join(dest, os.path.basename(source)))
|
|
70
|
+
|
|
71
|
+
# Skip if this is self.
|
|
72
|
+
if (os.path.exists(dest) and os.path.samefile(source, dest)):
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# Create any required parents.
|
|
76
|
+
os.makedirs(os.path.dirname(dest), exist_ok = True)
|
|
77
|
+
|
|
78
|
+
# Remove any existing dest.
|
|
79
|
+
if (os.path.exists(dest)):
|
|
80
|
+
remove(dest)
|
|
81
|
+
|
|
82
|
+
shutil.move(source, dest)
|
|
83
|
+
|
|
84
|
+
def copy(source: str, dest: str, dirs_exist_ok: bool = False) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Copy a file or directory into dest.
|
|
87
|
+
If source is a file, then dest can be a file or dir.
|
|
88
|
+
If source is a dir, it is copied as a subdirectory of dest.
|
|
89
|
+
If dirs_exist_ok is true, an existing destination directory is allowed.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
if (os.path.isfile(source) or os.path.islink(source)):
|
|
93
|
+
os.makedirs(os.path.dirname(dest), exist_ok = True)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
shutil.copy2(source, dest, follow_symlinks = False)
|
|
97
|
+
except shutil.SameFileError:
|
|
98
|
+
return
|
|
99
|
+
else:
|
|
100
|
+
if (os.path.isdir(dest)):
|
|
101
|
+
dest = os.path.join(dest, os.path.basename(source))
|
|
102
|
+
|
|
103
|
+
shutil.copytree(source, dest, dirs_exist_ok = dirs_exist_ok, symlinks = True)
|
|
104
|
+
|
|
105
|
+
def copy_contents(source: str, dest: str) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Copy a file or the contents of a directory (excluding the top-level directory) into dest.
|
|
108
|
+
For a file: `cp source dest/`
|
|
109
|
+
For a dir: `cp -r source/* dest/`
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
source = os.path.abspath(source)
|
|
113
|
+
|
|
114
|
+
if (os.path.isfile(source)):
|
|
115
|
+
copy(source, dest)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
for dirent in os.listdir(source):
|
|
119
|
+
source_path = os.path.join(source, dirent)
|
|
120
|
+
dest_path = os.path.join(dest, dirent)
|
|
121
|
+
|
|
122
|
+
if (os.path.isfile(source_path) or os.path.islink(source_path)):
|
|
123
|
+
copy(source_path, dest_path)
|
|
124
|
+
else:
|
|
125
|
+
shutil.copytree(source_path, dest_path, symlinks = True)
|
|
126
|
+
|
|
127
|
+
def read_file(path: str, strip: bool = True, encoding: str = DEAULT_ENCODING) -> str:
|
|
128
|
+
""" Read the contents of a file. """
|
|
129
|
+
|
|
130
|
+
with open(path, 'r', encoding = encoding) as file:
|
|
131
|
+
contents = file.read()
|
|
132
|
+
|
|
133
|
+
if (strip):
|
|
134
|
+
contents = contents.strip()
|
|
135
|
+
|
|
136
|
+
return contents
|
|
137
|
+
|
|
138
|
+
def write_file(path: str, contents: str, strip: bool = True, newline: bool = True, encoding: str = DEAULT_ENCODING) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Write the contents of a file.
|
|
141
|
+
Any existing file will be truncated.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
if (contents is None):
|
|
145
|
+
contents = ''
|
|
146
|
+
|
|
147
|
+
if (strip):
|
|
148
|
+
contents = contents.strip()
|
|
149
|
+
|
|
150
|
+
if (newline):
|
|
151
|
+
contents += "\n"
|
|
152
|
+
|
|
153
|
+
with open(path, 'w', encoding = encoding) as file:
|
|
154
|
+
file.write(contents)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
import edq.util.dirent
|
|
6
|
+
|
|
7
|
+
THIS_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
|
|
8
|
+
TEST_BASE_DIR = os.path.join(THIS_DIR, 'testdata', 'dirent-operations')
|
|
9
|
+
"""
|
|
10
|
+
This test data directory is laid out as:
|
|
11
|
+
.
|
|
12
|
+
├── a.txt
|
|
13
|
+
├── dir_1
|
|
14
|
+
│ ├── b.txt
|
|
15
|
+
│ └── dir_2
|
|
16
|
+
│ └── c.txt
|
|
17
|
+
├── dir_empty
|
|
18
|
+
├── file_empty
|
|
19
|
+
├── symlinklink_a.txt -> a.txt
|
|
20
|
+
├── symlinklink_dir_1 -> dir_1
|
|
21
|
+
└── symlinklink_dir_empty -> dir_empty
|
|
22
|
+
|
|
23
|
+
Where non-empty files are filled with their filename (without the extension).
|
|
24
|
+
dir_empty will not exist in the repository (since it is an empty directory),
|
|
25
|
+
but it will be created by _prep_temp_dir().
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
DIRENT_TYPE_DIR = 'dir'
|
|
29
|
+
DIRENT_TYPE_FILE = 'file'
|
|
30
|
+
|
|
31
|
+
class TestDirentOperations(unittest.TestCase):
|
|
32
|
+
""" Test basic operations on dirents. """
|
|
33
|
+
|
|
34
|
+
@unittest.skipIf((sys.platform.startswith("win") and (sys.version_info > (3, 11))), "windows symlink behavior")
|
|
35
|
+
def test_dirent_base(self):
|
|
36
|
+
""" Test that the base temp directory is properly setup. """
|
|
37
|
+
|
|
38
|
+
temp_dir = self._prep_temp_dir()
|
|
39
|
+
|
|
40
|
+
expected_paths = [
|
|
41
|
+
('a.txt', DIRENT_TYPE_FILE),
|
|
42
|
+
('dir_1', DIRENT_TYPE_DIR),
|
|
43
|
+
(os.path.join('dir_1', 'b.txt'), DIRENT_TYPE_FILE),
|
|
44
|
+
(os.path.join('dir_1', 'dir_2'), DIRENT_TYPE_DIR),
|
|
45
|
+
(os.path.join('dir_1', 'dir_2', 'c.txt'), DIRENT_TYPE_FILE),
|
|
46
|
+
('dir_empty', DIRENT_TYPE_DIR),
|
|
47
|
+
('file_empty', DIRENT_TYPE_FILE),
|
|
48
|
+
('symlinklink_a.txt', DIRENT_TYPE_FILE, True),
|
|
49
|
+
('symlinklink_dir_1', DIRENT_TYPE_DIR, True),
|
|
50
|
+
('symlinklink_dir_empty', DIRENT_TYPE_DIR, True),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
self._check_existing_paths(temp_dir, expected_paths)
|
|
54
|
+
|
|
55
|
+
def test_dirent_operations_remove(self):
|
|
56
|
+
""" Test removing dirents. """
|
|
57
|
+
|
|
58
|
+
temp_dir = self._prep_temp_dir()
|
|
59
|
+
|
|
60
|
+
# Remove these paths in this order.
|
|
61
|
+
remove_relpaths = [
|
|
62
|
+
# Symlink - File
|
|
63
|
+
'symlinklink_a.txt',
|
|
64
|
+
|
|
65
|
+
# Symlink - Dir
|
|
66
|
+
'symlinklink_dir_1',
|
|
67
|
+
|
|
68
|
+
# File in Directory
|
|
69
|
+
os.path.join('dir_1', 'dir_2', 'c.txt'),
|
|
70
|
+
|
|
71
|
+
# File
|
|
72
|
+
'a.txt',
|
|
73
|
+
|
|
74
|
+
# Empty File
|
|
75
|
+
'file_empty'
|
|
76
|
+
|
|
77
|
+
# Directory
|
|
78
|
+
'dir_1',
|
|
79
|
+
|
|
80
|
+
# Empty Directory
|
|
81
|
+
'dir_empty'
|
|
82
|
+
|
|
83
|
+
# Non-Existent
|
|
84
|
+
'ZZZ'
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
expected_paths = [
|
|
88
|
+
(os.path.join('dir_1', 'dir_2'), DIRENT_TYPE_DIR),
|
|
89
|
+
('file_empty', DIRENT_TYPE_FILE),
|
|
90
|
+
# Windows has some symlink issues, so we will not check for this file.
|
|
91
|
+
# ('symlinklink_dir_empty', DIRENT_TYPE_DIR, True),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
for relpath in remove_relpaths:
|
|
95
|
+
path = os.path.join(temp_dir, relpath)
|
|
96
|
+
edq.util.dirent.remove(path)
|
|
97
|
+
|
|
98
|
+
self._check_nonexisting_paths(temp_dir, remove_relpaths)
|
|
99
|
+
self._check_existing_paths(temp_dir, expected_paths)
|
|
100
|
+
|
|
101
|
+
def _prep_temp_dir(self):
|
|
102
|
+
temp_dir = edq.util.dirent.get_temp_dir(prefix = 'edq_test_dirent_')
|
|
103
|
+
edq.util.dirent.mkdir(os.path.join(temp_dir, 'dir_empty'))
|
|
104
|
+
edq.util.dirent.copy_contents(TEST_BASE_DIR, temp_dir)
|
|
105
|
+
return temp_dir
|
|
106
|
+
|
|
107
|
+
def _check_existing_paths(self, base_dir, raw_paths):
|
|
108
|
+
"""
|
|
109
|
+
Ensure that specific paths exists, and fail the test if they do not.
|
|
110
|
+
All paths should be relative to the base dir.
|
|
111
|
+
Paths can be:
|
|
112
|
+
- A string.
|
|
113
|
+
- A two-item tuple (path, dirent type).
|
|
114
|
+
- A three-item tuple (path, dirent type, is link?).
|
|
115
|
+
Missing components are not defaulted, they are just not checked.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
for raw_path in raw_paths:
|
|
119
|
+
relpath = ''
|
|
120
|
+
dirent_type = None
|
|
121
|
+
is_link = None
|
|
122
|
+
|
|
123
|
+
if (isinstance(raw_path, str)):
|
|
124
|
+
relpath = raw_path
|
|
125
|
+
elif (isinstance(raw_path, tuple)):
|
|
126
|
+
if (len(raw_path) not in [2, 3]):
|
|
127
|
+
raise ValueError(f"Expected exactly two or three items for path check, found {len(raw_path)} items: '{raw_path}'.")
|
|
128
|
+
|
|
129
|
+
relpath = raw_path[0]
|
|
130
|
+
dirent_type = raw_path[1]
|
|
131
|
+
|
|
132
|
+
if (len(raw_path) == 3):
|
|
133
|
+
is_link = raw_path[2]
|
|
134
|
+
else:
|
|
135
|
+
raise ValueError(f"Could not parse expected path ({type(raw_path)}): '{raw_path}'.")
|
|
136
|
+
|
|
137
|
+
path = os.path.join(base_dir, relpath)
|
|
138
|
+
|
|
139
|
+
# Check the path exists.
|
|
140
|
+
if (not os.path.exists(path)):
|
|
141
|
+
self.fail(f"Expected path does not exist: '{relpath}'.")
|
|
142
|
+
|
|
143
|
+
# Check the type of the dirent.
|
|
144
|
+
if (dirent_type is not None):
|
|
145
|
+
if (dirent_type == DIRENT_TYPE_DIR):
|
|
146
|
+
if (not os.path.isdir(path)):
|
|
147
|
+
self.fail(f"Expected path to be a directory, but it is not: '{relpath}'.")
|
|
148
|
+
elif (dirent_type == DIRENT_TYPE_FILE):
|
|
149
|
+
if (not os.path.isfile(path)):
|
|
150
|
+
self.fail(f"Expected path to be a file, but it is not: '{relpath}'.")
|
|
151
|
+
else:
|
|
152
|
+
raise ValueError(f"Unknown dirent type '{dirent_type}' for path: '{relpath}'.")
|
|
153
|
+
|
|
154
|
+
# Check the link status.
|
|
155
|
+
if (is_link is not None):
|
|
156
|
+
if (is_link != os.path.islink(path)):
|
|
157
|
+
self.fail(f"Expected path does not have a matching link status. Expected {is_link}, but is {not is_link}: '{relpath}'.")
|
|
158
|
+
|
|
159
|
+
def _check_nonexisting_paths(self, base_dir, raw_paths):
|
|
160
|
+
"""
|
|
161
|
+
Ensure that specific paths do not exists, and fail the test if they do exist.
|
|
162
|
+
All paths should be relative to the base dir.
|
|
163
|
+
Unlike _check_existing_paths(), paths should only be strings.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
for relpath in raw_paths:
|
|
167
|
+
path = os.path.join(base_dir, relpath)
|
|
168
|
+
|
|
169
|
+
if (os.path.exists(path)):
|
|
170
|
+
self.fail(f"Path exists when it should not: '{relpath}'.")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
b
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
c
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
b
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
c
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edq-utils
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Common utilities used by EduLinq Python projects.
|
|
5
|
+
Author-email: Eriq Augustine <eriq@edulinq.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 EduLinq
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/edulinq/python-utils
|
|
29
|
+
Project-URL: Repository, https://github.com/edulinq/python-utils
|
|
30
|
+
Keywords: education,utilities
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
34
|
+
Requires-Python: >=3.8
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: mypy>=1.14.1; extra == "dev"
|
|
39
|
+
Requires-Dist: pylint; extra == "dev"
|
|
40
|
+
Requires-Dist: twine; extra == "dev"
|
|
41
|
+
Requires-Dist: vermin; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# EduLinq Python Utilities
|
|
45
|
+
|
|
46
|
+
Common utilities used by EduLinq Python projects.
|
|
47
|
+
|
|
48
|
+
## Installation / Requirements
|
|
49
|
+
|
|
50
|
+
This project requires [Python](https://www.python.org/) >= 3.8.
|
|
51
|
+
|
|
52
|
+
The project can be installed from PyPi with:
|
|
53
|
+
```
|
|
54
|
+
pip3 install edq-utils
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Standard Python requirements are listed in `pyproject.toml`.
|
|
58
|
+
The project and Python dependencies can be installed from source with:
|
|
59
|
+
```
|
|
60
|
+
pip3 install .
|
|
61
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
requirements-dev.txt
|
|
5
|
+
requirements.txt
|
|
6
|
+
edq/__init__.py
|
|
7
|
+
edq/util/__init__.py
|
|
8
|
+
edq/util/dirent.py
|
|
9
|
+
edq/util/dirent_test.py
|
|
10
|
+
edq/util/testdata/dirent-operations/a.txt
|
|
11
|
+
edq/util/testdata/dirent-operations/file_empty
|
|
12
|
+
edq/util/testdata/dirent-operations/symlinklink_a.txt
|
|
13
|
+
edq/util/testdata/dirent-operations/dir_1/b.txt
|
|
14
|
+
edq/util/testdata/dirent-operations/dir_1/dir_2/c.txt
|
|
15
|
+
edq/util/testdata/dirent-operations/symlinklink_dir_1/b.txt
|
|
16
|
+
edq/util/testdata/dirent-operations/symlinklink_dir_1/dir_2/c.txt
|
|
17
|
+
edq_utils.egg-info/PKG-INFO
|
|
18
|
+
edq_utils.egg-info/SOURCES.txt
|
|
19
|
+
edq_utils.egg-info/dependency_links.txt
|
|
20
|
+
edq_utils.egg-info/requires.txt
|
|
21
|
+
edq_utils.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
edq
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [
|
|
4
|
+
"setuptools",
|
|
5
|
+
"packaging>=21.3",
|
|
6
|
+
"twine",
|
|
7
|
+
"vermin",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[project]
|
|
11
|
+
name = "edq-utils"
|
|
12
|
+
description = "Common utilities used by EduLinq Python projects."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
license = {file = "LICENSE"}
|
|
16
|
+
|
|
17
|
+
authors = [
|
|
18
|
+
{name = "Eriq Augustine", email = "eriq@edulinq.org"},
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
keywords = ['education', 'utilities']
|
|
22
|
+
classifiers = [
|
|
23
|
+
'Intended Audience :: Developers',
|
|
24
|
+
'License :: OSI Approved :: MIT License',
|
|
25
|
+
'Programming Language :: Python :: 3.8',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dynamic = [
|
|
29
|
+
"version",
|
|
30
|
+
"dependencies",
|
|
31
|
+
"optional-dependencies",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.dynamic]
|
|
35
|
+
version = {attr = "edq.__version__"}
|
|
36
|
+
dependencies = {file = ["requirements.txt"]}
|
|
37
|
+
optional-dependencies = {dev = {file = ["requirements-dev.txt"]}}
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/edulinq/python-utils"
|
|
41
|
+
Repository = "https://github.com/edulinq/python-utils"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools]
|
|
44
|
+
include-package-data = true
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-data]
|
|
47
|
+
"*" = ["*"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["."]
|
|
51
|
+
include = ["edq*"]
|
|
File without changes
|