Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
common_helper_files
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
fact-depend
common_helper_files
Commits
22f1745e
Unverified
Commit
22f1745e
authored
Dec 10, 2021
by
Jörg Stucke
Committed by
GitHub
Dec 10, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6 from maringuu/pathlib
Use pathlib
parents
86686b18
3909ba50
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
80 additions
and
113 deletions
+80
-113
__init__.py
common_helper_files/__init__.py
+8
-5
fail_safe_file_operations.py
common_helper_files/fail_safe_file_operations.py
+51
-82
file_functions.py
common_helper_files/file_functions.py
+18
-15
git_functions.py
common_helper_files/git_functions.py
+1
-1
setup.py
setup.py
+1
-1
test_fail_safe_file_operations.py
tests/test_fail_safe_file_operations.py
+1
-9
No files found.
common_helper_files/__init__.py
View file @
22f1745e
from
.fail_safe_file_operations
import
(
get_binary_from_file
,
get_string_list_from_file
,
write_binary_to_file
,
get_safe_name
,
delete_file
,
get_files_in_dir
,
get_dirs_in_dir
,
create_symlink
,
get_dir_of_file
,
safe_rglob
)
from
.file_functions
import
read_in_chunks
,
get_directory_for_filename
,
create_dir_for_file
,
human_readable_file_size
from
.fail_safe_file_operations
import
(
create_symlink
,
delete_file
,
get_binary_from_file
,
get_dir_of_file
,
get_dirs_in_dir
,
get_files_in_dir
,
get_safe_name
,
get_string_list_from_file
,
safe_rglob
,
write_binary_to_file
)
from
.file_functions
import
(
create_dir_for_file
,
get_directory_for_filename
,
human_readable_file_size
,
read_in_chunks
)
from
.git_functions
import
get_version_string_from_git
__all__
=
[
...
...
common_helper_files/fail_safe_file_operations.py
View file @
22f1745e
...
...
@@ -3,86 +3,67 @@ import os
import
re
import
sys
from
pathlib
import
Path
from
typing
import
Iterable
from
typing
import
Iterable
,
List
,
Union
from
.file_functions
import
create_dir_for_file
def
get_binary_from_file
(
file_path
)
:
def
get_binary_from_file
(
file_path
:
Union
[
str
,
Path
])
->
Union
[
str
,
bytes
]
:
'''
Fail-safe file read operation. Symbolic links are converted to text files including the link.
Errors are logged. No exception raised.
:param file_path: Path of the file. Can be absolute or relative to the current directory.
:type file_path: str
:return: file's binary as bytes; returns empty byte string on error
'''
try
:
if
os
.
path
.
islink
(
file_path
):
binary
=
'symbolic link -> {}'
.
format
(
os
.
readlink
(
file_path
))
path
=
Path
(
file_path
)
if
path
.
is_symlink
():
# We need to wait for python 3.9 for Path.readlink
binary
=
f
'symbolic link -> {os.readlink(path)}'
else
:
with
open
(
file_path
,
'rb'
)
as
f
:
binary
=
f
.
read
()
binary
=
path
.
read_bytes
()
except
Exception
as
e
:
logging
.
error
(
'Could not read file: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
)
)
logging
.
error
(
f
'Could not read file: {e}'
,
exc_info
=
True
)
binary
=
b
''
return
binary
def
get_string_list_from_file
(
file_path
)
:
def
get_string_list_from_file
(
file_path
:
Union
[
str
,
Path
])
->
List
[
str
]
:
'''
Fail-safe file read operation returning a list of text strings.
Errors are logged. No exception raised.
:param file_path: Path of the file. Can be absolute or relative to the current directory.
:type file_path: str
:return: file's content as text string list; returns empty list on error
'''
try
:
raw
=
get_binary_from_file
(
file_path
)
raw_string
=
raw
.
decode
(
encoding
=
'utf-8'
,
errors
=
'replace'
)
cleaned_string
=
_rm_cr
(
raw_string
)
cleaned_string
=
raw_string
.
replace
(
'
\r
'
,
''
)
return
cleaned_string
.
split
(
'
\n
'
)
except
Exception
as
e
:
logging
.
error
(
'Could not read file: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
))
return
[]
def
_rm_cr
(
input_string
):
return
input_string
.
replace
(
'
\r
'
,
''
)
def
write_binary_to_file
(
file_binary
,
file_path
,
overwrite
=
False
,
file_copy
=
False
):
def
write_binary_to_file
(
file_binary
:
Union
[
str
,
bytes
],
file_path
:
Union
[
str
,
Path
],
overwrite
:
bool
=
False
,
file_copy
:
bool
=
False
)
->
None
:
'''
Fail-safe file write operation. Creates directories if needed.
Errors are logged. No exception raised.
:param file_binary: binary to write into the file
:type file_binary: bytes or str
:param file_path: Path of the file. Can be absolute or relative to the current directory.
:type file_path: str
:param file_path_str: Path of the file. Can be absolute or relative to the current directory.
:param overwrite: overwrite file if it exists
:type overwrite: bool
:default overwrite: False
:param file_copy: If overwrite is false and file already exists, write into new file and add a counter to the file name.
:type file_copy: bool
:default file_copy: False
:return: None
'''
try
:
file_path
=
Path
(
file_path
)
create_dir_for_file
(
file_path
)
if
not
os
.
path
.
exists
(
file_path
)
or
overwrite
:
_write_file
(
file_path
,
file_binary
)
elif
file_copy
and
not
overwrite
:
new_path
=
_get_counted_file_path
(
file_path
)
_write_file
(
new_path
,
file_binary
)
except
Exception
as
e
:
logging
.
error
(
'Could not write file: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
))
def
_write_file
(
file_path
,
binary
):
with
open
(
file_path
,
'wb'
)
as
f
:
f
.
write
(
binary
)
if
file_path
.
exists
()
and
(
not
overwrite
or
file_copy
):
file_path
=
Path
(
_get_counted_file_path
(
str
(
file_path
)))
file_path
.
write_bytes
(
file_binary
)
except
Exception
as
exc
:
logging
.
error
(
f
'Could not write file: {exc}'
,
exc_info
=
True
)
def
_get_counted_file_path
(
original_path
):
...
...
@@ -95,55 +76,46 @@ def _get_counted_file_path(original_path):
return
new_file_path
def
delete_file
(
file_path
)
:
def
delete_file
(
file_path
:
Union
[
str
,
Path
])
->
None
:
'''
Fail-safe delete file operation. Deletes a file if it exists.
Errors are logged. No exception raised.
:param file_path: Path of the file. Can be absolute or relative to the current directory.
:type file_path: str
:return: None
'''
try
:
os
.
unlink
(
file_path
)
except
Exception
as
e
:
logging
.
error
(
'Could not delete file: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
)
)
Path
(
file_path
)
.
unlink
(
)
except
Exception
as
e
xc
:
logging
.
error
(
f
'Could not delete file: {exc}'
,
exc_info
=
True
)
def
create_symlink
(
src_path
,
dst_path
)
:
def
create_symlink
(
src_path
:
Union
[
str
,
Path
],
dst_path
:
Union
[
str
,
Path
])
->
None
:
'''
Fail-safe symlink operation. Symlinks a file if dest does not exist.
Errors are logged. No exception raised.
:param src_path: src file
:type src_path: str
:param dst_path: link location
:type dst_path: str
:return: None
'''
try
:
create_dir_for_file
(
dst_path
)
os
.
symlink
(
src_path
,
dst
_path
)
except
FileExistsError
as
e
:
logging
.
debug
(
'Could not create Link: File exists: {}'
.
format
(
e
)
)
except
Exception
as
e
:
logging
.
error
(
'Could not create link: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
)
)
Path
(
dst_path
)
.
symlink_to
(
src
_path
)
except
FileExistsError
as
e
xc
:
logging
.
debug
(
f
'Could not create Link: File exists: {exc}'
)
except
Exception
as
e
xc
:
logging
.
error
(
f
'Could not create link: {exc}'
,
exc_info
=
True
)
def
get_safe_name
(
file_name
,
max_size
=
200
,
valid_characters
=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+. '
)
:
def
get_safe_name
(
file_name
:
str
,
max_size
:
int
=
200
,
valid_characters
:
str
=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+. '
)
->
str
:
'''
removes all problematic characters from a file name
cuts file names if they are too long
:param file_name: Original file name
:type file_name: str
:param max_size: maximum allowed file name length
:type max_size: int
:default max_size: 200
:param valid_characters: characters that shall be allowed in a file name
:type valid_characters: str
:default valid_characters: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+. '
:return: str
'''
allowed_charachters
=
set
(
valid_characters
)
safe_name
=
filter
(
lambda
x
:
x
in
allowed_charachters
,
file_name
)
...
...
@@ -154,56 +126,53 @@ def get_safe_name(file_name, max_size=200, valid_characters='abcdefghijklmnopqrs
return
safe_name
def
get_files_in_dir
(
directory_path
)
:
def
get_files_in_dir
(
directory_path
:
Union
[
str
,
Path
])
->
List
[
str
]
:
'''
Returns a list with the absolute paths of all files in the directory directory_path
:param directory_path: directory including files
:type directory_path: str
:return: list
'''
result
=
[]
try
:
for
file_path
,
_
,
files
in
os
.
walk
(
directory_path
):
for
file_
in
files
:
result
.
append
(
os
.
path
.
abspath
(
os
.
path
.
join
(
file_path
,
file_
)))
except
Exception
as
e
:
logging
.
error
(
'Could not get files: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
)
)
result
.
append
(
str
(
Path
(
file_path
,
file_
)
.
absolute
(
)))
except
Exception
as
e
xc
:
logging
.
error
(
f
'Could not get files: {exc}'
,
exc_info
=
True
)
return
result
def
get_dirs_in_dir
(
directory_path
)
:
def
get_dirs_in_dir
(
directory_path
:
Union
[
str
,
Path
])
->
List
[
str
]
:
'''
Returns a list with the absolute paths of all 1st level sub-directories in the directory directory_path.
:param directory_path: directory including sub-directories
:type directory_path: str
:return: list
'''
result
=
[]
try
:
dir_content
=
os
.
listdir
(
directory_path
)
for
item
in
dir_content
:
dir_path
=
os
.
path
.
join
(
directory_path
,
item
)
if
os
.
path
.
isdir
(
dir_path
):
result
.
append
(
dir_path
)
except
Exception
as
e
:
logging
.
error
(
'Could not get directories: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
))
path
=
Path
(
directory_path
)
for
item
in
path
.
iterdir
()
:
if
Path
(
item
)
.
is_dir
():
result
.
append
(
str
(
item
.
resolve
()))
except
Exception
as
exc
:
logging
.
error
(
f
'Could not get directories: {exc}'
,
exc_info
=
True
)
return
result
def
get_dir_of_file
(
file_path
)
:
def
get_dir_of_file
(
file_path
:
Union
[
str
,
Path
])
->
str
:
'''
Returns absolute path of the directory including file
:param file_path: Path of the file
:type: path-like object
:return: string
.. deprecated::
You should use pathlib instead of this function.
'''
try
:
return
os
.
path
.
dirname
(
os
.
path
.
abspath
(
file_path
)
)
except
Exception
as
e
:
logging
.
error
(
'Could not get directory path: {} {}'
.
format
(
sys
.
exc_info
()[
0
]
.
__name__
,
e
)
)
return
str
(
Path
(
file_path
)
.
resolve
()
.
parent
)
except
Exception
as
e
xc
:
logging
.
error
(
f
'Could not get directory path: {exc}'
,
exc_info
=
True
)
return
'/'
...
...
@@ -231,7 +200,7 @@ def _iterate_path_recursively(path: Path, include_symlinks: bool = True, include
for
child_path
in
path
.
iterdir
():
yield
from
_iterate_path_recursively
(
child_path
,
include_symlinks
,
include_directories
)
except
PermissionError
:
logging
.
error
(
'Permission Error: could not access path {path}'
.
format
(
path
=
path
.
absolute
())
)
logging
.
error
(
f
'Permission Error: could not access path {path.absolute()}'
)
except
OSError
:
logging
.
warning
(
'possible broken symlink: {path}'
.
format
(
path
=
path
.
absolute
())
)
logging
.
warning
(
f
'possible broken symlink: {path.absolute()}'
)
yield
from
[]
common_helper_files/file_functions.py
View file @
22f1745e
import
os
import
io
from
pathlib
import
Path
from
typing
import
Type
,
Union
import
bitmath
def
read_in_chunks
(
file_object
,
chunk_size
=
1024
)
:
def
read_in_chunks
(
file_object
:
Type
[
io
.
BufferedReader
],
chunk_size
=
1024
)
->
bytes
:
'''
Helper function to read large file objects iteratively in smaller chunks. Can be used like this::
...
...
@@ -12,7 +15,6 @@ def read_in_chunks(file_object, chunk_size=1024):
:param file_object: The file object from which the chunk data is read. Must be a subclass of ``io.BufferedReader``.
:param chunk_size: Number of bytes to read per chunk.
:type chunk_size: int
:return: Returns a generator to iterate over all chunks, see above for usage.
'''
while
True
:
...
...
@@ -22,35 +24,36 @@ def read_in_chunks(file_object, chunk_size=1024):
yield
data
def
get_directory_for_filename
(
filename
)
:
def
get_directory_for_filename
(
filename
:
Union
[
str
,
Path
])
->
str
:
'''
Convenience function which returns the absolute path to the directory that contains the given file name.
:param filename: Path of the file. Can be absolute or relative to the current directory.
:type filename: str
:return: Absolute path of the directory
.. deprecated::
You should use pathlib instead of this function.
'''
return
os
.
path
.
dirname
(
os
.
path
.
abspath
(
filename
)
)
return
str
(
Path
(
filename
)
.
resolve
()
.
parent
)
def
create_dir_for_file
(
file_path
)
:
def
create_dir_for_file
(
file_path
:
Union
[
str
,
Path
])
->
None
:
'''
Creates all directories of file path. File path may include the file as well.
:param file_path: Path of the file. Can be absolute or relative to the current directory.
:type file_path: str
:return: None
.. deprecated::
You should use pathlib instead of this function.
'''
directory
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
file_path
))
os
.
makedirs
(
directory
,
exist_ok
=
True
)
Path
(
file_path
)
.
resolve
()
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
def
human_readable_file_size
(
size_in_bytes
)
:
def
human_readable_file_size
(
size_in_bytes
:
int
)
->
str
:
'''
Returns a nicly human readable file size
Returns a nic
e
ly human readable file size
:param size_in_bytes: Size in Bytes
:type size_in_bytes: int
:return: str
'''
return
bitmath
.
Byte
(
bytes
=
size_in_bytes
)
.
best_prefix
()
.
format
(
'{value:.2f} {unit}'
)
common_helper_files/git_functions.py
View file @
22f1745e
import
subprocess
def
get_version_string_from_git
(
directory_name
)
:
def
get_version_string_from_git
(
directory_name
:
str
)
->
str
:
return
subprocess
.
check_output
([
'git'
,
'describe'
,
'--always'
],
cwd
=
directory_name
)
.
strip
()
.
decode
(
'utf-8'
)
setup.py
View file @
22f1745e
from
setuptools
import
setup
,
find_packages
VERSION
=
'0.
2.3
'
VERSION
=
'0.
3.0
'
setup
(
name
=
'common_helper_files'
,
...
...
tests/test_fail_safe_file_operations.py
View file @
22f1745e
...
...
@@ -8,7 +8,7 @@ from common_helper_files import (
create_symlink
,
delete_file
,
get_safe_name
,
get_binary_from_file
,
get_dir_of_file
,
get_directory_for_filename
,
get_dirs_in_dir
,
get_files_in_dir
,
get_string_list_from_file
,
safe_rglob
,
write_binary_to_file
)
from
common_helper_files.fail_safe_file_operations
import
_get_counted_file_path
,
_rm_cr
from
common_helper_files.fail_safe_file_operations
import
_get_counted_file_path
TEST_DATA_DIR
=
Path
(
__file__
)
.
absolute
()
.
parent
/
'data'
EMPTY_FOLDER
=
TEST_DATA_DIR
/
'empty_folder'
...
...
@@ -188,11 +188,3 @@ def test_safe_rglob_empty_dir():
assert
EMPTY_FOLDER
.
exists
()
result
=
safe_rglob
(
EMPTY_FOLDER
)
assert
len
(
list
(
result
))
==
0
@pytest.mark.parametrize
(
'input_data, expected'
,
[
(
'abc'
,
'abc'
),
(
'ab
\r\n
c'
,
'ab
\n
c'
),
])
def
test_rm_cr
(
input_data
,
expected
):
assert
_rm_cr
(
input_data
)
==
expected
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment