Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fa031f6ee | ||
|
|
f38cce1c1d | ||
|
|
52dd1e7b73 | ||
|
|
661a182655 | ||
|
|
d803de312d | ||
|
|
57a36d64f1 | ||
|
|
0cc4883fa1 | ||
|
|
1eb464dd2c | ||
|
|
f900a6eab9 |
14
ReadMe.md
14
ReadMe.md
@@ -20,7 +20,11 @@ This is a pip package that can be installed into any project and covers the foll
|
|||||||
- logging_handling: extend log and also error message handling
|
- logging_handling: extend log and also error message handling
|
||||||
- requests_handling: requests wrapper for better calls with auth headers
|
- requests_handling: requests wrapper for better calls with auth headers
|
||||||
- script_handling: pid lock file handling, abort timer
|
- script_handling: pid lock file handling, abort timer
|
||||||
- string_handling: byte format, datetime format, hashing, string formats for numbrers, etc
|
- string_handling: byte format, datetime format, hashing, string formats for numbrers, double byte string format, etc
|
||||||
|
|
||||||
|
## UV setup
|
||||||
|
|
||||||
|
uv must be [installed](https://docs.astral.sh/uv/getting-started/installation/)
|
||||||
|
|
||||||
## How to publish
|
## How to publish
|
||||||
|
|
||||||
@@ -41,7 +45,7 @@ uv publish --index egra-gitea --token <gitea token> --native-tls
|
|||||||
|
|
||||||
## Test package
|
## Test package
|
||||||
|
|
||||||
We must set the full index URL here because we run with "--no-project2
|
We must set the full index URL here because we run with "--no-project"
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
uv run --with corelibs --index egra-gitea=https://git.egplusww.jp/api/packages/PyPI/pypi/simple/ --no-project --native-tls -- python -c "import corelibs"
|
uv run --with corelibs --index egra-gitea=https://git.egplusww.jp/api/packages/PyPI/pypi/simple/ --no-project --native-tls -- python -c "import corelibs"
|
||||||
@@ -51,12 +55,16 @@ uv run --with corelibs --index egra-gitea=https://git.egplusww.jp/api/packages/P
|
|||||||
|
|
||||||
In the test folder other tests are located.
|
In the test folder other tests are located.
|
||||||
|
|
||||||
At the moment only a small test for the "progress" module is set
|
At the moment only a small test for the "progress" and the "double byte string format" module is set
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
uv run --native-tls tests/progress/progress_test.py
|
uv run --native-tls tests/progress/progress_test.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
uv run --native-tls tests/double_byte_string_format/double_byte_string_format.py
|
||||||
|
```
|
||||||
|
|
||||||
## How to install in another project
|
## How to install in another project
|
||||||
|
|
||||||
This will also add the index entry
|
This will also add the index entry
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# MARK: Project info
|
# MARK: Project info
|
||||||
[project]
|
[project]
|
||||||
name = "corelibs"
|
name = "corelibs"
|
||||||
version = "0.3.1"
|
version = "0.5.0"
|
||||||
description = "Collection of utils for Python scripts"
|
description = "Collection of utils for Python scripts"
|
||||||
readme = "ReadMe.md"
|
readme = "ReadMe.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|||||||
@@ -89,3 +89,5 @@ class CsvWriter:
|
|||||||
csv_row[value] = line[key]
|
csv_row[value] = line[key]
|
||||||
self.csv_file_writer.writerow(csv_row)
|
self.csv_file_writer.writerow(csv_row)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -110,5 +110,4 @@ class Timer:
|
|||||||
"""
|
"""
|
||||||
return self._run_time
|
return self._run_time
|
||||||
|
|
||||||
|
|
||||||
# __END__
|
# __END__
|
||||||
@@ -42,3 +42,5 @@ def file_name_crc(file_path: Path, add_parent_folder: bool = False) -> str:
|
|||||||
return str(Path(file_path.parent.name).joinpath(file_path.name))
|
return str(Path(file_path.parent.name).joinpath(file_path.name))
|
||||||
else:
|
else:
|
||||||
return file_path.name
|
return file_path.name
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -126,3 +126,5 @@ def value_lookup(haystack: dict[str, str], value: str, raise_on_many: bool = Fal
|
|||||||
if raise_on_many is True and len(keys) > 1:
|
if raise_on_many is True and len(keys) > 1:
|
||||||
raise ValueError("More than one element found with the same name")
|
raise ValueError("More than one element found with the same name")
|
||||||
return keys[0]
|
return keys[0]
|
||||||
|
|
||||||
|
# __END__
|
||||||
37
src/corelibs/list_dict_handling/dict_helpers.py
Normal file
37
src/corelibs/list_dict_handling/dict_helpers.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
Dict helpers
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def mask(
|
||||||
|
data_set: dict[str, str],
|
||||||
|
mask_keys: list[str] | None = None,
|
||||||
|
mask_str: str = "***",
|
||||||
|
skip: bool = False
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""
|
||||||
|
mask data for output
|
||||||
|
Checks if mask_keys list exist in any key in the data set either from the start or at the end
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
data_set {dict[str, str]} -- _description_
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
mask_keys {list[str] | None} -- _description_ (default: {None})
|
||||||
|
mask_str {str} -- _description_ (default: {"***"})
|
||||||
|
skip {bool} -- _description_ (default: {False})
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, str] -- _description_
|
||||||
|
"""
|
||||||
|
if skip is True:
|
||||||
|
return data_set
|
||||||
|
if mask_keys is None:
|
||||||
|
mask_keys = ["password", "secret"]
|
||||||
|
return {
|
||||||
|
key: mask_str
|
||||||
|
if any(key.startswith(mask_key) or key.endswith(mask_key) for mask_key in mask_keys) else value
|
||||||
|
for key, value in data_set.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -35,3 +35,5 @@ def dict_hash_crc(data: dict[Any, Any] | list[Any]) -> str:
|
|||||||
return hashlib.sha256(
|
return hashlib.sha256(
|
||||||
json.dumps(data, sort_keys=True, ensure_ascii=True).encode('utf-8')
|
json.dumps(data, sort_keys=True, ensure_ascii=True).encode('utf-8')
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -59,3 +59,5 @@ def build_dict(
|
|||||||
dict[str, Any | list[Any] | dict[Any, Any]],
|
dict[str, Any | list[Any] | dict[Any, Any]],
|
||||||
delete_keys_from_set(any_dict, ignore_entries)
|
delete_keys_from_set(any_dict, ignore_entries)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -30,6 +30,8 @@ class Log:
|
|||||||
self.logger = logging.getLogger(log_name)
|
self.logger = logging.getLogger(log_name)
|
||||||
# set maximum logging level for all logging output
|
# set maximum logging level for all logging output
|
||||||
self.logger.setLevel(logging.DEBUG)
|
self.logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# self.handlers = []
|
||||||
# console logger
|
# console logger
|
||||||
self.__console_handler(log_level_console)
|
self.__console_handler(log_level_console)
|
||||||
# file logger
|
# file logger
|
||||||
@@ -74,8 +76,8 @@ class Log:
|
|||||||
formatter_file_handler = logging.Formatter(
|
formatter_file_handler = logging.Formatter(
|
||||||
(
|
(
|
||||||
'[%(asctime)s.%(msecs)03d] '
|
'[%(asctime)s.%(msecs)03d] '
|
||||||
'[%(pathname)s:%(funcName)s:%(lineno)d] '
|
|
||||||
'[%(name)s:%(process)d] '
|
'[%(name)s:%(process)d] '
|
||||||
|
'[%(pathname)s:%(funcName)s:%(lineno)d] '
|
||||||
'<%(levelname)s> '
|
'<%(levelname)s> '
|
||||||
'%(message)s'
|
'%(message)s'
|
||||||
),
|
),
|
||||||
@@ -93,3 +93,5 @@ def unlock_run(lock_file: Path) -> None:
|
|||||||
lock_file.unlink()
|
lock_file.unlink()
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise IOError(f"Cannot remove lock_file: {lock_file}: {e}") from e
|
raise IOError(f"Cannot remove lock_file: {lock_file}: {e}") from e
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -60,5 +60,4 @@ def create_time(timestamp: float, timestamp_format: str = "%Y-%m-%d %H:%M:%S") -
|
|||||||
"""
|
"""
|
||||||
return time.strftime(timestamp_format, time.localtime(timestamp))
|
return time.strftime(timestamp_format, time.localtime(timestamp))
|
||||||
|
|
||||||
|
|
||||||
# __END__
|
# __END__
|
||||||
226
src/corelibs/string_handling/double_byte_string_format.py
Normal file
226
src/corelibs/string_handling/double_byte_string_format.py
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
"""
|
||||||
|
Format double byte strings to exact length
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
|
||||||
|
class DoubleByteFormatString:
|
||||||
|
"""
|
||||||
|
Format a string to exact length
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
string: str,
|
||||||
|
cut_length: int,
|
||||||
|
format_length: int | None = None,
|
||||||
|
placeholder: str = '..',
|
||||||
|
format_string: str = '{{:<{len}}}'
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
shorts a string to exact cut length and sets it to format length
|
||||||
|
|
||||||
|
after "cut_length" cut the "placeholder" will be added, so that the new cut_length is never
|
||||||
|
larget than the cut_length given (".." is counted to cut_length)
|
||||||
|
if format_length if set and outside format_length will be set
|
||||||
|
the cut_length is adjusted to format_length if the format_length is shorter
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
"Foo bar baz" 10 charcters -> 5 cut_length -> 10 format_length
|
||||||
|
"Foo.. "
|
||||||
|
|
||||||
|
use class.get_string_short() for cut length shortend string
|
||||||
|
use class.get_string_short_formated() to get the shorted string to format length padding
|
||||||
|
|
||||||
|
creates a class that shortens and sets the format length
|
||||||
|
to use with a print format run the format needs to be pre set in
|
||||||
|
the style of {{:<{len}}} style
|
||||||
|
self.get_string_short_formated() for the "len" parameter
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): string to work with
|
||||||
|
cut_length (int): width to shorten to
|
||||||
|
format_length (int | None): format length. Defaults to None
|
||||||
|
placeholder (str, optional): placeholder to put after shortened string. Defaults to '..'.
|
||||||
|
format_string (str, optional): format string. Defaults to '{{:<{len}}}'
|
||||||
|
"""
|
||||||
|
# output variables
|
||||||
|
self.string_short: str = ''
|
||||||
|
self.string_width_value: int = 0
|
||||||
|
self.string_short_width: int = 0
|
||||||
|
self.format_length_value: int = 0
|
||||||
|
# internal varaibles
|
||||||
|
self.placeholder: str = placeholder
|
||||||
|
# original string
|
||||||
|
self.string: str = ''
|
||||||
|
# width to cut string to
|
||||||
|
self.cut_length: int = 0
|
||||||
|
# format length to set to
|
||||||
|
self.format_length: int = 0
|
||||||
|
# main string
|
||||||
|
self.string = str(string)
|
||||||
|
|
||||||
|
self.format_string: str = format_string
|
||||||
|
|
||||||
|
# if width is > 0 set, else set width of string (fallback)
|
||||||
|
if cut_length > 0:
|
||||||
|
self.cut_length = cut_length
|
||||||
|
elif cut_length <= 0:
|
||||||
|
self.cut_length = self.__string_width_calc(self.string)
|
||||||
|
# format length set, if not set or smaller than 0, set to width of string
|
||||||
|
self.format_length = self.cut_length
|
||||||
|
if format_length is not None and format_length > 0:
|
||||||
|
self.format_length = format_length
|
||||||
|
# check that width is not larger then length if yes, set width to length
|
||||||
|
self.cut_length = min(self.cut_length, self.format_length)
|
||||||
|
|
||||||
|
# process the string shorten and format length calculation
|
||||||
|
self.process()
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
"""
|
||||||
|
runs all the class methods to set string length, the string shortened
|
||||||
|
and the format length
|
||||||
|
"""
|
||||||
|
# call the internal ones to set the data
|
||||||
|
if self.string:
|
||||||
|
self.__string_width()
|
||||||
|
self.__shorten_string()
|
||||||
|
if self.format_length:
|
||||||
|
self.__format_length()
|
||||||
|
|
||||||
|
def get_string_short(self) -> str:
|
||||||
|
"""
|
||||||
|
get the shortend string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str -- _description_
|
||||||
|
"""
|
||||||
|
return self.string_short
|
||||||
|
|
||||||
|
def get_string_short_formated(self, format_string: str = '{{:<{len}}}') -> str:
|
||||||
|
"""
|
||||||
|
get the formatted string
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
format_string {_type_} -- _description_ (default: {'{{:<{len}}}'})
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str -- _description_
|
||||||
|
"""
|
||||||
|
if not format_string:
|
||||||
|
format_string = self.format_string
|
||||||
|
return format_string.format(
|
||||||
|
len=self.get_format_length()
|
||||||
|
).format(
|
||||||
|
self.get_string_short()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_format_length(self) -> int:
|
||||||
|
"""
|
||||||
|
get the format length for outside length set
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int -- _description_
|
||||||
|
"""
|
||||||
|
return self.format_length_value
|
||||||
|
|
||||||
|
def get_cut_length(self) -> int:
|
||||||
|
"""
|
||||||
|
get the actual cut length
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int -- _description_
|
||||||
|
"""
|
||||||
|
return self.cut_length
|
||||||
|
|
||||||
|
def get_requested_cut_length(self) -> int:
|
||||||
|
"""
|
||||||
|
get the requested cut length
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int -- _description_
|
||||||
|
"""
|
||||||
|
return self.cut_length
|
||||||
|
|
||||||
|
def get_requested_format_length(self) -> int:
|
||||||
|
"""
|
||||||
|
get the requested format length
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int -- _description_
|
||||||
|
"""
|
||||||
|
return self.format_length
|
||||||
|
|
||||||
|
def __string_width_calc(self, string: str) -> int:
|
||||||
|
"""
|
||||||
|
does the actual string width calculation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): string to calculate from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: stringth width
|
||||||
|
"""
|
||||||
|
return sum(1 + (unicodedata.east_asian_width(c) in "WF") for c in string)
|
||||||
|
|
||||||
|
def __string_width(self):
|
||||||
|
"""
|
||||||
|
calculates the string width based on the characters
|
||||||
|
this is an internal method and should not be called on itself
|
||||||
|
"""
|
||||||
|
# only run if string is set and is valid string
|
||||||
|
if self.string:
|
||||||
|
# calculate width. add +1 for each double byte character
|
||||||
|
self.string_width_value = self.__string_width_calc(self.string)
|
||||||
|
|
||||||
|
def __format_length(self):
|
||||||
|
"""
|
||||||
|
set the format length based on the length for the format
|
||||||
|
and the shortend string
|
||||||
|
this is an internal method and should not be called on itself
|
||||||
|
"""
|
||||||
|
if not self.string_short:
|
||||||
|
self.__shorten_string()
|
||||||
|
# get correct format length based on string
|
||||||
|
if (
|
||||||
|
self.string_short and
|
||||||
|
self.format_length > 0 and
|
||||||
|
self.string_short_width > 0
|
||||||
|
):
|
||||||
|
# length: format length wanted
|
||||||
|
# substract the width of the shortend string - the length of the shortend string
|
||||||
|
self.format_length_value = self.format_length - (self.string_short_width - len(self.string_short))
|
||||||
|
else:
|
||||||
|
# if we have nothing to shorten the length, keep the old one
|
||||||
|
self.format_length_value = self.format_length
|
||||||
|
|
||||||
|
def __shorten_string(self):
|
||||||
|
"""
|
||||||
|
shorten string down to set width
|
||||||
|
this is an internal method and should not be called on itself
|
||||||
|
"""
|
||||||
|
# set string width if not set
|
||||||
|
if not self.string_width_value:
|
||||||
|
self.__string_width()
|
||||||
|
# if the double byte string width is larger than the wanted width
|
||||||
|
if self.string_width_value > self.cut_length:
|
||||||
|
cur_len = 0
|
||||||
|
self.string_short = ''
|
||||||
|
for char in str(self.string):
|
||||||
|
# set the current length if we add the character
|
||||||
|
cur_len += 2 if unicodedata.east_asian_width(char) in "WF" else 1
|
||||||
|
# if the new length is smaller than the output length to shorten too add the char
|
||||||
|
if cur_len <= (self.cut_length - len(self.placeholder)):
|
||||||
|
self.string_short += char
|
||||||
|
self.string_short_width = cur_len
|
||||||
|
# return string with new width and placeholder
|
||||||
|
self.string_short = f"{self.string_short}{self.placeholder}"
|
||||||
|
self.string_short_width += len(self.placeholder)
|
||||||
|
else:
|
||||||
|
# if string is same saze just copy
|
||||||
|
self.string_short = self.string
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -36,3 +36,5 @@ def sha1_short(string: str) -> str:
|
|||||||
str -- _description_
|
str -- _description_
|
||||||
"""
|
"""
|
||||||
return hashlib.sha1(string.encode('utf-8')).hexdigest()[:9]
|
return hashlib.sha1(string.encode('utf-8')).hexdigest()[:9]
|
||||||
|
|
||||||
|
# __END__
|
||||||
66
tests/double_byte_string_format/double_byte_string_format.py
Normal file
66
tests/double_byte_string_format/double_byte_string_format.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env -S uv run --script
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test for double byte format
|
||||||
|
"""
|
||||||
|
|
||||||
|
from corelibs.string_handling.double_byte_string_format import DoubleByteFormatString
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Main call
|
||||||
|
"""
|
||||||
|
string = [
|
||||||
|
"Some string 123 other text",
|
||||||
|
"Some string 日本語 other text",
|
||||||
|
"日本語は string 123 other text",
|
||||||
|
"あいうえおかきくけこさしすせそなにぬねのまみむめも〜",
|
||||||
|
"あいうえおかきくけこさしす 1 other text",
|
||||||
|
"Some string すせそなにぬねのまみむめも〜",
|
||||||
|
"SOME OTHER STRING THAT IS LONGER THAN TWENTYSIX CHARACTERS",
|
||||||
|
"日本語は string 123 other text Some string 日本語 other text"
|
||||||
|
]
|
||||||
|
|
||||||
|
format_str = "{{:<{len}}}"
|
||||||
|
|
||||||
|
length_set = [
|
||||||
|
(26, 25),
|
||||||
|
(26, 26),
|
||||||
|
(26, 60),
|
||||||
|
(26, 20),
|
||||||
|
(26, -5),
|
||||||
|
(-6, -5),
|
||||||
|
]
|
||||||
|
|
||||||
|
for _length_set in length_set:
|
||||||
|
cut_length = _length_set[0]
|
||||||
|
format_length = _length_set[1]
|
||||||
|
print(f"========= Cut: {cut_length} | Format: {format_length} ==> ")
|
||||||
|
for _string in string:
|
||||||
|
string_test = DoubleByteFormatString(_string, cut_length, format_length)
|
||||||
|
formated = format_str.format(
|
||||||
|
len=string_test.get_format_length()
|
||||||
|
).format(
|
||||||
|
string_test.get_string_short()
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"* Shorten string: shorten length: "
|
||||||
|
f"Req: {string_test.get_requested_cut_length()} ({cut_length}) / "
|
||||||
|
f"Set: {string_test.get_cut_length()}, "
|
||||||
|
"format length: "
|
||||||
|
f"Req: {string_test.get_requested_format_length()} ({format_length}) / "
|
||||||
|
f"Set: {string_test.get_format_length()}"
|
||||||
|
f"\nOrig: |{_string}|"
|
||||||
|
f"\nGSS : |{string_test.get_string_short()}|"
|
||||||
|
f"\nF : |{formated}|"
|
||||||
|
f"\nGSSF: |{string_test.get_string_short_formated()}|"
|
||||||
|
)
|
||||||
|
print("-------")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
# __END__
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env -S uv run --script
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Test for progress class
|
Test for progress class
|
||||||
@@ -89,3 +89,5 @@ def main():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
# __END__
|
||||||
|
|||||||
Reference in New Issue
Block a user