Z0Z-tools 0.5.2__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.
- Z0Z_tools/Z0Z_dataStructure.py +85 -0
- Z0Z_tools/Z0Z_io.py +23 -0
- Z0Z_tools/Z0Z_ioAudio.py +123 -0
- Z0Z_tools/__init__.py +19 -0
- Z0Z_tools/pipAnything.py +156 -0
- Z0Z_tools-0.5.2.dist-info/METADATA +68 -0
- Z0Z_tools-0.5.2.dist-info/RECORD +12 -0
- Z0Z_tools-0.5.2.dist-info/WHEEL +5 -0
- Z0Z_tools-0.5.2.dist-info/top_level.txt +2 -0
- unittests/test_Z0Z_dataStructure.py +340 -0
- unittests/test_ioAudio.py +59 -0
- unittests/test_pipAnything.py +59 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
def stringItUp(*scrapPile: Any) -> List[str]:
|
|
4
|
+
"""
|
|
5
|
+
Recursively extracts all elements from nested data structures and converts the elements to strings.
|
|
6
|
+
Order is not preserved.
|
|
7
|
+
Parameters:
|
|
8
|
+
*scrapPile: One or more data structures to unpack and convert to strings.
|
|
9
|
+
Returns:
|
|
10
|
+
listStrungUp: A list of string versions of all elements in the input data structure.
|
|
11
|
+
"""
|
|
12
|
+
listStrungUp = []
|
|
13
|
+
|
|
14
|
+
def drill(KitKat: Any) -> None:
|
|
15
|
+
if isinstance(KitKat, str):
|
|
16
|
+
listStrungUp.append(KitKat)
|
|
17
|
+
elif isinstance(KitKat, (bool, bytearray, bytes, complex, float, int, memoryview, type(None))):
|
|
18
|
+
listStrungUp.append(str(KitKat))
|
|
19
|
+
elif isinstance(KitKat, dict):
|
|
20
|
+
for broken, piece in KitKat.items():
|
|
21
|
+
drill(broken)
|
|
22
|
+
drill(piece)
|
|
23
|
+
elif isinstance(KitKat, (frozenset, list, range, set, tuple)):
|
|
24
|
+
for kit in KitKat:
|
|
25
|
+
drill(kit)
|
|
26
|
+
elif hasattr(KitKat, '__iter__'): # Unpack other iterables
|
|
27
|
+
for kat in KitKat:
|
|
28
|
+
drill(kat)
|
|
29
|
+
else:
|
|
30
|
+
try:
|
|
31
|
+
sharingIsCaring = KitKat.__str__()
|
|
32
|
+
listStrungUp.append(sharingIsCaring)
|
|
33
|
+
except AttributeError:
|
|
34
|
+
pass
|
|
35
|
+
except TypeError: # "The error traceback provided indicates that there is an issue when calling the __str__ method on an object that does not have this method properly defined, leading to a TypeError."
|
|
36
|
+
pass
|
|
37
|
+
try:
|
|
38
|
+
for scrap in scrapPile:
|
|
39
|
+
drill(scrap)
|
|
40
|
+
except RecursionError:
|
|
41
|
+
listStrungUp.append(repr(scrap))
|
|
42
|
+
return listStrungUp
|
|
43
|
+
|
|
44
|
+
def updateExtendPolishDictionaryLists(*dictionaryLists: Dict[str, List[Any]], destroyDuplicates: bool = False, reorderLists: bool = False, \
|
|
45
|
+
killErroneousDataTypes: bool = False) -> Dict[str, List[Any]]:
|
|
46
|
+
"""
|
|
47
|
+
Merges multiple dictionaries containing lists into a single dictionary, with options to handle duplicates,
|
|
48
|
+
list ordering, and erroneous data types.
|
|
49
|
+
Parameters:
|
|
50
|
+
*dictionaryLists: Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
|
|
51
|
+
destroyDuplicates (False): If True, removes duplicate elements from the lists. Defaults to False.
|
|
52
|
+
ignoreListOrdering (False): If True, sorts the lists. Defaults to False.
|
|
53
|
+
killErroneousDataTypes (False): If True, skips lists that cause a TypeError during merging. Defaults to False.
|
|
54
|
+
Returns:
|
|
55
|
+
ePluribusUnum: A single dictionary with merged lists based on the provided options. If only one dictionary is passed,
|
|
56
|
+
it will be cleaned up based on the options.
|
|
57
|
+
Note:
|
|
58
|
+
The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`typing.Dict`). Furthermore, every dictionary key is a
|
|
59
|
+
so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive list (`typing.List`). If `dictionaryLists`
|
|
60
|
+
has other data types, the data types will not be preserved. That could have unexpected consequences: in some cases, for example, conversion
|
|
61
|
+
from the original data type to a `typing.List` will not preserve the order even if you want the order preserved.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
ePluribusUnum: Dict[str, List[Any]] = {}
|
|
65
|
+
|
|
66
|
+
for dictionaryListTarget in dictionaryLists:
|
|
67
|
+
for keyName, keyValue in dictionaryListTarget.items():
|
|
68
|
+
try:
|
|
69
|
+
ImaStr = str(keyName)
|
|
70
|
+
ImaList = list(keyValue)
|
|
71
|
+
ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
|
|
72
|
+
except TypeError:
|
|
73
|
+
if killErroneousDataTypes:
|
|
74
|
+
continue
|
|
75
|
+
else:
|
|
76
|
+
raise
|
|
77
|
+
|
|
78
|
+
if destroyDuplicates:
|
|
79
|
+
for ImaStr, ImaList in ePluribusUnum.items():
|
|
80
|
+
ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
|
|
81
|
+
if reorderLists:
|
|
82
|
+
for ImaStr, ImaList in ePluribusUnum.items():
|
|
83
|
+
ePluribusUnum[ImaStr] = sorted(ImaList)
|
|
84
|
+
|
|
85
|
+
return ePluribusUnum
|
Z0Z_tools/Z0Z_io.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Iterable, Any, Union
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
def dataTabularTOpathFilenameDelimited(pathFilename: Union[str, os.PathLike[Any]], tableRows: Iterable[Iterable[Any]], \
|
|
5
|
+
tableColumns: Iterable[Any], delimiterOutput: str = '\t') -> None:
|
|
6
|
+
"""
|
|
7
|
+
Writes tabular data to a delimited file. This is a low-quality function: you'd probably be better off with something else.
|
|
8
|
+
Parameters:
|
|
9
|
+
pathFilename: The path and filename where the data will be written.
|
|
10
|
+
tableRows: The rows of the table, where each row is a list of strings or floats.
|
|
11
|
+
tableColumns: The column headers for the table.
|
|
12
|
+
delimiterOutput (tab): The delimiter to use in the output file. Defaults to *tab*.
|
|
13
|
+
Returns:
|
|
14
|
+
None:
|
|
15
|
+
|
|
16
|
+
This function still exists because I have not refactored `analyzeAudio.analyzeAudioListPathFilenames()`. The structure of
|
|
17
|
+
that function's returned data is easily handled by this function. See https://github.com/hunterhogan/analyzeAudio
|
|
18
|
+
"""
|
|
19
|
+
with open(pathFilename, 'w', newline='') as writeStream:
|
|
20
|
+
writeStream.write(delimiterOutput.join(tableColumns) + '\n')
|
|
21
|
+
|
|
22
|
+
for row in tableRows:
|
|
23
|
+
writeStream.write(delimiterOutput.join(map(str, row)) + '\n')
|
Z0Z_tools/Z0Z_ioAudio.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from numpy.typing import NDArray
|
|
2
|
+
from typing import Any, BinaryIO, Dict, List, Sequence, Tuple, Union
|
|
3
|
+
import io
|
|
4
|
+
import numpy
|
|
5
|
+
import os
|
|
6
|
+
import pathlib
|
|
7
|
+
import samplerate
|
|
8
|
+
import soundfile
|
|
9
|
+
|
|
10
|
+
def loadWaveforms(listPathFilenames: Union[Sequence[str], Sequence[os.PathLike[str]]], sampleRate: int = 44100) -> NDArray[numpy.float32]:
|
|
11
|
+
"""
|
|
12
|
+
Load a list of audio files into a single array.
|
|
13
|
+
Parameters:
|
|
14
|
+
listPathFilenames: List of file paths to the audio files.
|
|
15
|
+
sampleRate (44100): Target sample rate for the waveforms; the function will resample if necessary. Defaults to 44100.
|
|
16
|
+
Returns:
|
|
17
|
+
arrayWaveforms: A single NumPy array of shape (COUNTchannels, COUNTsamplesMaximum, COUNTwaveforms)
|
|
18
|
+
"""
|
|
19
|
+
axisOrderMapping: Dict[str, int] = {'indexingAxis': -1, 'axisTime': -2, 'axisChannels': 0}
|
|
20
|
+
axesSizes: Dict[str, int] = {keyName: 1 for keyName in axisOrderMapping.keys()}
|
|
21
|
+
COUNTaxes: int = len(axisOrderMapping)
|
|
22
|
+
listShapeIndexToSize: List[int] = [9001] * COUNTaxes
|
|
23
|
+
|
|
24
|
+
COUNTwaveforms: int = len(listPathFilenames)
|
|
25
|
+
axesSizes['indexingAxis'] = COUNTwaveforms
|
|
26
|
+
COUNTchannels: int = 2
|
|
27
|
+
axesSizes['axisChannels'] = COUNTchannels
|
|
28
|
+
|
|
29
|
+
listCOUNTsamples: List[int] = []
|
|
30
|
+
axisTime: int = 0
|
|
31
|
+
|
|
32
|
+
for pathFilename in listPathFilenames:
|
|
33
|
+
with soundfile.SoundFile(pathFilename) as readSoundFile:
|
|
34
|
+
sampleRateSoundFile: int = readSoundFile.samplerate
|
|
35
|
+
waveform: NDArray[numpy.float32] = readSoundFile.read(dtype='float32', always_2d=True).astype(numpy.float32)
|
|
36
|
+
if sampleRateSoundFile != sampleRate:
|
|
37
|
+
waveform = resampleWaveform(waveform, sampleRate, sampleRateSoundFile)
|
|
38
|
+
listCOUNTsamples.append(waveform.shape[axisTime])
|
|
39
|
+
|
|
40
|
+
COUNTsamplesMaximum: int = max(listCOUNTsamples)
|
|
41
|
+
axesSizes['axisTime'] = COUNTsamplesMaximum
|
|
42
|
+
|
|
43
|
+
for keyName, axisSize in axesSizes.items():
|
|
44
|
+
axisNormalized: int = (axisOrderMapping[keyName] + COUNTaxes) % COUNTaxes
|
|
45
|
+
listShapeIndexToSize[axisNormalized] = axisSize
|
|
46
|
+
tupleShapeArray: Tuple[int, ...] = tuple(listShapeIndexToSize)
|
|
47
|
+
|
|
48
|
+
# `numpy.zeros` so that shorter waveforms are safely padded with zeros
|
|
49
|
+
arrayWaveforms: NDArray[numpy.float32] = numpy.zeros(tupleShapeArray, dtype=numpy.float32)
|
|
50
|
+
|
|
51
|
+
for index in range(COUNTwaveforms):
|
|
52
|
+
with soundfile.SoundFile(listPathFilenames[index]) as readSoundFile:
|
|
53
|
+
sampleRateSoundFile: int = readSoundFile.samplerate
|
|
54
|
+
waveform: NDArray[numpy.float32] = readSoundFile.read(dtype='float32', always_2d=True).astype(numpy.float32)
|
|
55
|
+
|
|
56
|
+
if sampleRateSoundFile != sampleRate:
|
|
57
|
+
waveform = resampleWaveform(waveform, sampleRate, sampleRateSoundFile)
|
|
58
|
+
|
|
59
|
+
COUNTsamples: int = waveform.shape[axisTime]
|
|
60
|
+
arrayWaveforms[:, 0:COUNTsamples, index] = waveform.T
|
|
61
|
+
|
|
62
|
+
return arrayWaveforms
|
|
63
|
+
|
|
64
|
+
def readAudioFile(pathFilename: Union[str, os.PathLike[Any], BinaryIO], sampleRate: int = 44100) -> NDArray[numpy.float32]:
|
|
65
|
+
"""
|
|
66
|
+
Reads an audio file and returns its data as a NumPy array. Mono is always converted to stereo.
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
pathFilename: The path to the audio file.
|
|
70
|
+
sampleRate (44100): The sample rate to use when reading the file. Defaults to 44100.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
waveform: The audio data in an array shaped (channels, samples).
|
|
74
|
+
"""
|
|
75
|
+
with soundfile.SoundFile(pathFilename) as readStream:
|
|
76
|
+
sampleRateSource: int = readStream.samplerate
|
|
77
|
+
waveform: NDArray[numpy.float32] = readStream.read(dtype='float32', always_2d=True).astype(numpy.float32)
|
|
78
|
+
waveform = resampleWaveform(waveform, sampleRateDesired=sampleRate, sampleRateSource=sampleRateSource)
|
|
79
|
+
return waveform.T
|
|
80
|
+
|
|
81
|
+
def resampleWaveform(waveform: NDArray[numpy.float32], sampleRateDesired: int, sampleRateSource: int) -> NDArray[numpy.float32]:
|
|
82
|
+
"""
|
|
83
|
+
Resamples the waveform to the desired sample rate.
|
|
84
|
+
|
|
85
|
+
Parameters:
|
|
86
|
+
waveform: The input audio data.
|
|
87
|
+
sampleRateDesired: The desired sample rate.
|
|
88
|
+
sampleRateSource: The original sample rate of the waveform.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
waveformResampled: The resampled waveform.
|
|
92
|
+
"""
|
|
93
|
+
if sampleRateSource != sampleRateDesired:
|
|
94
|
+
converter: str = 'sinc_best'
|
|
95
|
+
ratio: float = sampleRateDesired / sampleRateSource
|
|
96
|
+
waveformResampled: NDArray[numpy.float32] = samplerate.resample(waveform, ratio, converter)
|
|
97
|
+
return waveformResampled
|
|
98
|
+
else:
|
|
99
|
+
return waveform
|
|
100
|
+
|
|
101
|
+
def writeWav(pathFilename: Union[str, os.PathLike[Any], io.IOBase], waveform: NDArray[Any], sampleRate: int = 44100) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Writes a waveform to a WAV file.
|
|
104
|
+
|
|
105
|
+
Parameters:
|
|
106
|
+
pathFilename: The path and filename where the WAV file will be saved.
|
|
107
|
+
waveform: The waveform data to be written to the WAV file. The waveform should be in the shape (channels, samples).
|
|
108
|
+
sampleRate (44100): The sample rate of the waveform. Defaults to 44100 Hz.
|
|
109
|
+
|
|
110
|
+
Notes:
|
|
111
|
+
The function will create any necessary directories if they do not exist.
|
|
112
|
+
The function will overwrite the file if it already exists without prompting or informing the user.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
None:
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(pathFilename, io.IOBase):
|
|
119
|
+
try:
|
|
120
|
+
pathlib.Path(pathFilename).parent.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
except OSError:
|
|
122
|
+
pass
|
|
123
|
+
soundfile.write(file=pathFilename, data=waveform.T, samplerate=sampleRate, subtype='FLOAT', format='WAV')
|
Z0Z_tools/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__author__ = "Hunter Hogan"
|
|
2
|
+
__version__ = "0.5.2"
|
|
3
|
+
|
|
4
|
+
from Z0Z_tools.pipAnything import installPackageTarget, makeListRequirementsFromRequirementsFile
|
|
5
|
+
from Z0Z_tools.Z0Z_dataStructure import stringItUp, updateExtendPolishDictionaryLists
|
|
6
|
+
from Z0Z_tools.Z0Z_io import dataTabularTOpathFilenameDelimited
|
|
7
|
+
from Z0Z_tools.Z0Z_ioAudio import writeWav, readAudioFile, loadWaveforms
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'dataTabularTOpathFilenameDelimited',
|
|
11
|
+
'installPackageTarget',
|
|
12
|
+
'loadWaveforms',
|
|
13
|
+
'makeListRequirementsFromRequirementsFile',
|
|
14
|
+
'readAudioFile',
|
|
15
|
+
'stringItUp',
|
|
16
|
+
'updateExtendPolishDictionaryLists',
|
|
17
|
+
'writeWav',
|
|
18
|
+
]
|
|
19
|
+
|
Z0Z_tools/pipAnything.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions:
|
|
3
|
+
- installPackageTarget: Tries to trick pip into installing the package from a given directory.
|
|
4
|
+
- makeListRequirementsFromRequirementsFile: Reads a requirements.txt file, discards anything it couldn't understand, and creates a list of packages.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from pipAnything import installPackageTarget
|
|
8
|
+
installPackageTarget('path/to/packageTarget')
|
|
9
|
+
|
|
10
|
+
pip will attempt to install requirements.txt, but don't rely on dependencies being installed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from packaging.requirements import Requirement
|
|
14
|
+
from typing import List, Union
|
|
15
|
+
import os
|
|
16
|
+
import pathlib
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
|
|
21
|
+
def makeListRequirementsFromRequirementsFile(*pathFilenames: Union[str, os.PathLike[str]]) -> List[str]:
|
|
22
|
+
"""
|
|
23
|
+
Reads one or more requirements files and extracts valid package requirements.
|
|
24
|
+
Parameters:
|
|
25
|
+
*pathFilenames: One or more paths to requirements files.
|
|
26
|
+
Returns:
|
|
27
|
+
listRequirements: A list of unique, valid package requirements found in the provided files.
|
|
28
|
+
The function performs the following steps:
|
|
29
|
+
1. Iterates over each provided file path.
|
|
30
|
+
2. Checks if the file exists.
|
|
31
|
+
3. Reads the file line by line, removing comments and trimming whitespace.
|
|
32
|
+
4. Skips lines that are empty or contain spaces/tabs after sanitization.
|
|
33
|
+
5. Validates if the sanitized line is a valid requirement.
|
|
34
|
+
6. Collects valid requirements and removes duplicates before returning the list.
|
|
35
|
+
"""
|
|
36
|
+
listRequirements = []
|
|
37
|
+
|
|
38
|
+
for pathFilename in pathFilenames:
|
|
39
|
+
if pathlib.Path(pathFilename).exists():
|
|
40
|
+
try:
|
|
41
|
+
filesystemObjectRead = open(pathFilename, 'r')
|
|
42
|
+
for commentedLine in filesystemObjectRead:
|
|
43
|
+
sanitizedLine = commentedLine.split('#')[0].strip() # Remove comments and trim whitespace
|
|
44
|
+
|
|
45
|
+
# Skip lines that are empty or contain spaces/tabs after sanitization
|
|
46
|
+
if "\t" in sanitizedLine or " " in sanitizedLine or not sanitizedLine:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
# Validate if it's a valid requirement
|
|
50
|
+
try:
|
|
51
|
+
Requirement(sanitizedLine)
|
|
52
|
+
listRequirements.append(sanitizedLine)
|
|
53
|
+
except:
|
|
54
|
+
pass # Skip invalid requirement lines
|
|
55
|
+
finally:
|
|
56
|
+
filesystemObjectRead.close()
|
|
57
|
+
|
|
58
|
+
return sorted(set(listRequirements)) # Remove duplicates
|
|
59
|
+
|
|
60
|
+
def make_setupDOTpy(relativePathPackage: Union[str, os.PathLike[str]], listRequirements: List[str]) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Generates setup.py file content for installing the package.
|
|
63
|
+
|
|
64
|
+
Parameters:
|
|
65
|
+
relativePathPackage: The relative path to the package directory.
|
|
66
|
+
listRequirements: A list of requirements to be included in install_requires.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str: The setup.py content to be written to a file.
|
|
70
|
+
"""
|
|
71
|
+
return rf"""
|
|
72
|
+
import os
|
|
73
|
+
from setuptools import setup, find_packages
|
|
74
|
+
|
|
75
|
+
setup(
|
|
76
|
+
name='{pathlib.Path(relativePathPackage).name}',
|
|
77
|
+
version='0.0.0',
|
|
78
|
+
packages=find_packages(where=r'{relativePathPackage}'),
|
|
79
|
+
package_dir={{'': r'{relativePathPackage}'}},
|
|
80
|
+
install_requires={listRequirements},
|
|
81
|
+
include_package_data=True,
|
|
82
|
+
)
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def installPackageTarget(pathPackageTarget: Union[str, os.PathLike[str]]) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Installs a package by creating a temporary setup.py and tricking pip into installing it.
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
pathPackageTarget: The directory path of the package to be installed.
|
|
91
|
+
"""
|
|
92
|
+
filenameRequirementsHARDCODED = pathlib.Path('requirements.txt')
|
|
93
|
+
filenameRequirements = pathlib.Path(filenameRequirementsHARDCODED)
|
|
94
|
+
|
|
95
|
+
pathPackage = pathlib.Path(pathPackageTarget).resolve()
|
|
96
|
+
pathSystemTemporary = pathlib.Path(tempfile.mkdtemp())
|
|
97
|
+
pathFilename_setupDOTpy = pathSystemTemporary / 'setup.py'
|
|
98
|
+
|
|
99
|
+
pathFilenameRequirements = pathPackage / filenameRequirements
|
|
100
|
+
listRequirements = makeListRequirementsFromRequirementsFile(pathFilenameRequirements)
|
|
101
|
+
|
|
102
|
+
# Try-finally block for file handling: with-as doesn't always work
|
|
103
|
+
writeStream = None
|
|
104
|
+
try:
|
|
105
|
+
writeStream = pathFilename_setupDOTpy.open(mode='w')
|
|
106
|
+
relativePathPackage = pathPackage.relative_to(pathSystemTemporary, walk_up=True).as_posix()
|
|
107
|
+
writeStream.write(make_setupDOTpy(relativePathPackage, listRequirements))
|
|
108
|
+
finally:
|
|
109
|
+
if writeStream:
|
|
110
|
+
writeStream.close()
|
|
111
|
+
|
|
112
|
+
# Run pip to install the package from the temporary directory
|
|
113
|
+
subprocessPython = subprocess.Popen(
|
|
114
|
+
# `pip` needs a RELATIVE PATH, not an absolute path, and not a path+filename.
|
|
115
|
+
args=[sys.executable, '-m', 'pip', 'install', str(pathSystemTemporary)],
|
|
116
|
+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Output the subprocess stdout in real-time
|
|
120
|
+
if subprocessPython.stdout:
|
|
121
|
+
for lineStdout in subprocessPython.stdout:
|
|
122
|
+
print(lineStdout, end="")
|
|
123
|
+
|
|
124
|
+
subprocessPython.wait()
|
|
125
|
+
|
|
126
|
+
# Clean up by removing setup.py
|
|
127
|
+
pathFilename_setupDOTpy.unlink()
|
|
128
|
+
|
|
129
|
+
def everyone_knows_what___main___is() -> None:
|
|
130
|
+
"""A rudimentary CLI for the module.
|
|
131
|
+
call `installPackageTarget` from other modules."""
|
|
132
|
+
packageTarget = sys.argv[1] if len(sys.argv) > 1 else ''
|
|
133
|
+
pathPackageTarget = pathlib.Path(packageTarget)
|
|
134
|
+
if not pathPackageTarget.is_dir() or len(sys.argv) != 2:
|
|
135
|
+
namespaceModule = pathlib.Path(__file__).stem
|
|
136
|
+
namespacePackage = pathlib.Path(__file__).parent.stem
|
|
137
|
+
print(f"\n{namespaceModule} says, 'That didn't work. Try again?'\n\n"
|
|
138
|
+
f"Usage:\tpython -m {namespacePackage}.{namespaceModule} <packageTarget>\n"
|
|
139
|
+
f"\t<packageTarget> is a path to a directory with Python modules\n"
|
|
140
|
+
f"\tExample: python -m {namespacePackage}.{namespaceModule} '{pathlib.PurePath('path' ,'to', 'Z0Z_tools')}'")
|
|
141
|
+
# What is `-m`? Obviously, `-m` creates a namespace for the module, which is obviously necessary, except when it isn't.
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
installPackageTarget(pathPackageTarget)
|
|
145
|
+
print(f"\n{pathlib.Path(__file__).stem} finished trying to trick pip into installing {pathPackageTarget.name}. Did it work?")
|
|
146
|
+
|
|
147
|
+
def readability_counts() -> None:
|
|
148
|
+
"""Brings the snark."""
|
|
149
|
+
everyone_knows_what___main___is()
|
|
150
|
+
|
|
151
|
+
def main() -> None:
|
|
152
|
+
"""Jabs subtly."""
|
|
153
|
+
readability_counts()
|
|
154
|
+
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: Z0Z_tools
|
|
3
|
+
Version: 0.5.2
|
|
4
|
+
Summary: Audio processing, data structure, and package management tools.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hunterhogan/Z0Z_tools
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: packaging
|
|
10
|
+
Requires-Dist: samplerate
|
|
11
|
+
Requires-Dist: soundfile
|
|
12
|
+
|
|
13
|
+
# Z0Z_tools
|
|
14
|
+
|
|
15
|
+
"Z0Z_"- is a placeholder, and a Z0Z_tool is at best, a prototype.
|
|
16
|
+
|
|
17
|
+
## Install an arbitrary package with `pipAnything`
|
|
18
|
+
|
|
19
|
+
Try to install a package that doesn't have installation files.
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
python -m Z0Z_tools.pipAnything <pathPackage>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Unpack and convert elements to `str` types with `stringItUp`
|
|
26
|
+
|
|
27
|
+
## Merge and/or lightly clean a dictionary of lists with `updateExtendPolishDictionaryLists`
|
|
28
|
+
|
|
29
|
+
- Merges multiple dictionaries of lists into a single dictionary.
|
|
30
|
+
- Optionally remove duplicates each list.
|
|
31
|
+
- Optionally sort each list.
|
|
32
|
+
- Optionally delete data that won't merge.
|
|
33
|
+
|
|
34
|
+
## Basic read/write WAV files with `readAudioFile` and `writeWav`
|
|
35
|
+
|
|
36
|
+
The only option is the sample rate.
|
|
37
|
+
|
|
38
|
+
## Use `loadWaveforms` to create one array of waveforms from multiple files
|
|
39
|
+
|
|
40
|
+
## Install this package
|
|
41
|
+
|
|
42
|
+
### From Github
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install Z0Z_tools@git+https://github.com/hunterhogan/Z0Z_tools.git
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From a local directory
|
|
49
|
+
|
|
50
|
+
#### Windows
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
git clone https://github.com/hunterhogan/Z0Z_tools.git \path\to\Z0Z_tools
|
|
54
|
+
pip install Z0Z_tools@file:\path\to\Z0Z_tools
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### POSIX
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/hunterhogan/Z0Z_tools.git /path/to/Z0Z_tools
|
|
61
|
+
pip install Z0Z_tools@file:/path/to/Z0Z_tools
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Install updates
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pip install --upgrade Z0Z_tools@git+https://github.com/hunterhogan/Z0Z_tools.git
|
|
68
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Z0Z_tools/Z0Z_dataStructure.py,sha256=_JAMowiDupWVTbRgonau8O8UTvZDHuNTk61EbX-a3R4,4098
|
|
2
|
+
Z0Z_tools/Z0Z_io.py,sha256=OPwSM04xhyLTuGwhAR73i4UxmMC9tGS-z4d1HstXjUk,1229
|
|
3
|
+
Z0Z_tools/Z0Z_ioAudio.py,sha256=L8F23JpGmG8KE-tDiRQsBgoJF2bPHYclk-K2w6CwzAU,5465
|
|
4
|
+
Z0Z_tools/__init__.py,sha256=U6auRjRMpVKba_awePT9ipumvmnK6UB185_gHBQBucM,622
|
|
5
|
+
Z0Z_tools/pipAnything.py,sha256=HenP4K_fBAdurrRGgKCFkS4HIqFNj6guw_BZl2tkjMU,6380
|
|
6
|
+
unittests/test_Z0Z_dataStructure.py,sha256=mInwhLhX_9jaLy8IMPSt4Vfec9J14ntnbjHEvrTqeNo,14841
|
|
7
|
+
unittests/test_ioAudio.py,sha256=nyOu_oY74uPQIwpR-JkhEJ1IinLjIt2_TiUmG-7qBgA,2736
|
|
8
|
+
unittests/test_pipAnything.py,sha256=55QG_HoDVQQT9uvB43yLxVz-fY6rugv4ODcmsd9a_oU,2668
|
|
9
|
+
Z0Z_tools-0.5.2.dist-info/METADATA,sha256=uZK7cJTYpumIeF3invq3pfgvbU2pcIAF-qtagarLOt4,1687
|
|
10
|
+
Z0Z_tools-0.5.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
11
|
+
Z0Z_tools-0.5.2.dist-info/top_level.txt,sha256=0QGn7IRzNFffddo0Wg128tqn55DbM2H47940hEgqgFg,20
|
|
12
|
+
Z0Z_tools-0.5.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
from itertools import count, islice
|
|
4
|
+
from Z0Z_tools.Z0Z_dataStructure import updateExtendPolishDictionaryLists, stringItUp
|
|
5
|
+
import datetime
|
|
6
|
+
import numpy
|
|
7
|
+
import unittest
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
class TestStringItUp(unittest.TestCase):
|
|
11
|
+
|
|
12
|
+
def test_empty_input(self):
|
|
13
|
+
self.assertEqual(stringItUp(), [])
|
|
14
|
+
|
|
15
|
+
def test_with_bytearray(self):
|
|
16
|
+
self.assertEqual(stringItUp(bytearray(b"bytearray")), ["bytearray(b'bytearray')"])
|
|
17
|
+
|
|
18
|
+
def test_with_generator(self):
|
|
19
|
+
def gen():
|
|
20
|
+
for i in range(3):
|
|
21
|
+
yield i
|
|
22
|
+
self.assertEqual(stringItUp(gen()), ['0', '1', '2'])
|
|
23
|
+
|
|
24
|
+
def test_with_infinite_iterator(self):
|
|
25
|
+
infinite_gen = count()
|
|
26
|
+
limited_gen = islice(infinite_gen, 5)
|
|
27
|
+
self.assertEqual(stringItUp(limited_gen), ['0', '1', '2', '3', '4'])
|
|
28
|
+
|
|
29
|
+
def test_with_object_with_custom_iter(self):
|
|
30
|
+
class CustomIter:
|
|
31
|
+
def __iter__(self):
|
|
32
|
+
return iter([1, 2, 3])
|
|
33
|
+
self.assertEqual(stringItUp(CustomIter()), ['1', '2', '3'])
|
|
34
|
+
|
|
35
|
+
def test_with_recursive_structure(self):
|
|
36
|
+
chicken_tikka_masala = []
|
|
37
|
+
chicken_tikka_masala.append(chicken_tikka_masala)
|
|
38
|
+
self.assertEqual(stringItUp(chicken_tikka_masala), ['[[...]]'])
|
|
39
|
+
|
|
40
|
+
def test_with_recursive_structure2(self):
|
|
41
|
+
chicken_tikka_masala = []
|
|
42
|
+
chicken_tikka_masala.append(chicken_tikka_masala)
|
|
43
|
+
self.assertEqual(stringItUp([chicken_tikka_masala, 'wazzup']), ["[[[...]], 'wazzup']"])
|
|
44
|
+
|
|
45
|
+
def test_with_nan_and_inf(self):
|
|
46
|
+
self.assertEqual(stringItUp(float('nan'), float('inf')), ['nan', 'inf'])
|
|
47
|
+
|
|
48
|
+
def test_with_large_numbers(self):
|
|
49
|
+
large_number = 10**100
|
|
50
|
+
self.assertEqual(stringItUp(large_number), [str(large_number)])
|
|
51
|
+
|
|
52
|
+
def test_with_decimal(self):
|
|
53
|
+
self.assertEqual(stringItUp(Decimal('1.1')), ['1.1'])
|
|
54
|
+
|
|
55
|
+
def test_with_fraction(self):
|
|
56
|
+
self.assertEqual(stringItUp(Fraction(1, 3)), ['1/3'])
|
|
57
|
+
|
|
58
|
+
def test_with_dates(self):
|
|
59
|
+
date = datetime.date(2021, 1, 1)
|
|
60
|
+
self.assertEqual(stringItUp(date), ['2021-01-01'])
|
|
61
|
+
|
|
62
|
+
def test_with_times(self):
|
|
63
|
+
time = datetime.time(12, 34, 56)
|
|
64
|
+
self.assertEqual(stringItUp(time), ['12:34:56'])
|
|
65
|
+
|
|
66
|
+
def test_with_datetime(self):
|
|
67
|
+
dt = datetime.datetime(2021, 1, 1, 12, 34, 56)
|
|
68
|
+
self.assertEqual(stringItUp(dt), ['2021-01-01 12:34:56'])
|
|
69
|
+
|
|
70
|
+
def test_with_uuid(self):
|
|
71
|
+
my_uuid = uuid.uuid4()
|
|
72
|
+
self.assertEqual(stringItUp(my_uuid), [str(my_uuid)])
|
|
73
|
+
|
|
74
|
+
def test_with_memoryview(self):
|
|
75
|
+
result = stringItUp(memoryview(b"memoryview"))
|
|
76
|
+
expected_prefix = "<memory at 0x"
|
|
77
|
+
self.assertTrue(result[0].startswith(expected_prefix))
|
|
78
|
+
|
|
79
|
+
def test_empty_iterable_types(self):
|
|
80
|
+
self.assertEqual(stringItUp([], (), set()), [])
|
|
81
|
+
|
|
82
|
+
def test_mixed_nested_iterables(self):
|
|
83
|
+
data = [1, (2, {3, "four"}), {"five": [6, 7]}]
|
|
84
|
+
self.assertCountEqual(stringItUp(data), ["1", "2", "3", "four", "five", "6", "7"])
|
|
85
|
+
|
|
86
|
+
def test_large_data(self):
|
|
87
|
+
large_list = list(range(1000))
|
|
88
|
+
result = stringItUp(large_list)
|
|
89
|
+
self.assertEqual(len(result), 1000)
|
|
90
|
+
for i in range(1000):
|
|
91
|
+
self.assertEqual(result[i], str(i))
|
|
92
|
+
|
|
93
|
+
class TestUpdateExtendDictionaryLists(unittest.TestCase):
|
|
94
|
+
|
|
95
|
+
def test_with_mixed_types_in_values(self):
|
|
96
|
+
primus = {'a': [1, 'two'], 'b': [True, None]}
|
|
97
|
+
secundus = {'a': [3.14, 'four'], 'b': [False, 'none']}
|
|
98
|
+
expected = {
|
|
99
|
+
'a': [1, 'two', 3.14, 'four'],
|
|
100
|
+
'b': [True, None, False, 'none']
|
|
101
|
+
}
|
|
102
|
+
result = updateExtendPolishDictionaryLists(primus, secundus)
|
|
103
|
+
self.assertEqual(result, expected)
|
|
104
|
+
|
|
105
|
+
def test_with_non_string_keys(self):
|
|
106
|
+
primus = {None: [3], True: [2]}
|
|
107
|
+
secundus = {1: [1], (4, 5): [4]}
|
|
108
|
+
expected ={'(4, 5)': [4], '1': [1], 'None': [3], 'True': [2]}
|
|
109
|
+
result = updateExtendPolishDictionaryLists(primus, secundus) # type: ignore
|
|
110
|
+
self.assertEqual(result, expected)
|
|
111
|
+
|
|
112
|
+
def test_with_conflicting_data_types(self):
|
|
113
|
+
primus = {'a': 1, 'b': 2}
|
|
114
|
+
secundus = {'a': 3, 'c': 4}
|
|
115
|
+
with self.assertRaises(TypeError):
|
|
116
|
+
result = updateExtendPolishDictionaryLists(primus, secundus) # type: ignore
|
|
117
|
+
|
|
118
|
+
def test_with_kill_erroneous_data_types(self):
|
|
119
|
+
primus = {'a': [1, 2], 'b': [3, 4]}
|
|
120
|
+
secundus = {'a': 3, 'c': 4}
|
|
121
|
+
expected = {'a': [1, 2], 'b': [3, 4]}
|
|
122
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, killErroneousDataTypes=True) # type: ignore
|
|
123
|
+
self.assertEqual(result, expected)
|
|
124
|
+
|
|
125
|
+
def test_basic_functionality(self):
|
|
126
|
+
primus = {'a': [3, 1], 'b': [2]}
|
|
127
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
128
|
+
expected = {'a': [3, 1, 9, 6, 1, 22, 3], 'b': [2, 111111, 2, 3]}
|
|
129
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=False)
|
|
130
|
+
self.assertEqual(result, expected)
|
|
131
|
+
|
|
132
|
+
def test_ignore_list_ordering(self):
|
|
133
|
+
primus = {'a': [3, 1], 'b': [2]}
|
|
134
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
135
|
+
expected = {'a': [1, 1, 3, 3, 6, 9, 22], 'b': [2, 2, 3, 111111]}
|
|
136
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=True)
|
|
137
|
+
self.assertEqual(result, expected)
|
|
138
|
+
|
|
139
|
+
def test_destroy_duplicates(self):
|
|
140
|
+
primus = {'a': [3, 1], 'b': [2]}
|
|
141
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
142
|
+
expected = {'a': [3, 1, 9, 6, 22], 'b': [2, 111111, 3]}
|
|
143
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=True, reorderLists=False)
|
|
144
|
+
self.assertEqual(result, expected)
|
|
145
|
+
|
|
146
|
+
def test_single_dictionary_secundus2(self):
|
|
147
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
148
|
+
expected = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
149
|
+
result = updateExtendPolishDictionaryLists({}, secundus, destroyDuplicates=False, reorderLists=False)
|
|
150
|
+
self.assertEqual(result, expected)
|
|
151
|
+
|
|
152
|
+
def test_single_dictionary_secundus_ignore_list_ordering2(self):
|
|
153
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
154
|
+
expected = {'a': [1, 3, 6, 9, 22], 'b': [2, 3, 111111]}
|
|
155
|
+
result = updateExtendPolishDictionaryLists({}, secundus, destroyDuplicates=False, reorderLists=True)
|
|
156
|
+
self.assertEqual(result, expected)
|
|
157
|
+
|
|
158
|
+
def test_single_dictionary_secundus_destroy_duplicates2(self):
|
|
159
|
+
secundus = {'a': [9, 6, 1, 22, 3, 9], 'b': [111111, 2, 3, 2]}
|
|
160
|
+
expected = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
161
|
+
result = updateExtendPolishDictionaryLists({}, secundus, destroyDuplicates=True, reorderLists=False)
|
|
162
|
+
self.assertEqual(result, expected)
|
|
163
|
+
|
|
164
|
+
def test_single_dictionary_secundus_destroy_duplicates_ignore_list_ordering2(self):
|
|
165
|
+
secundus = {'a': [9, 6, 1, 22, 3, 9], 'b': [111111, 2, 3, 2]}
|
|
166
|
+
expected = {'a': [1, 3, 6, 9, 22], 'b': [2, 3, 111111]}
|
|
167
|
+
result = updateExtendPolishDictionaryLists({}, secundus, destroyDuplicates=True, reorderLists=True)
|
|
168
|
+
self.assertEqual(result, expected)
|
|
169
|
+
|
|
170
|
+
def test_empty_dictionaries(self):
|
|
171
|
+
primus = {}
|
|
172
|
+
secundus = {}
|
|
173
|
+
expected = {}
|
|
174
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=False)
|
|
175
|
+
self.assertEqual(result, expected)
|
|
176
|
+
|
|
177
|
+
def test_empty_and_non_empty_dictionary(self):
|
|
178
|
+
primus = {}
|
|
179
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
180
|
+
expected = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
181
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=False)
|
|
182
|
+
self.assertEqual(result, expected)
|
|
183
|
+
|
|
184
|
+
def test_non_empty_and_empty_dictionary(self):
|
|
185
|
+
primus = {'a': [3, 1], 'b': [2]}
|
|
186
|
+
secundus = {}
|
|
187
|
+
expected = {'a': [3, 1], 'b': [2]}
|
|
188
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=False)
|
|
189
|
+
self.assertEqual(result, expected)
|
|
190
|
+
|
|
191
|
+
def test_empty_dictionaries_with_options(self):
|
|
192
|
+
primus = {}
|
|
193
|
+
secundus = {}
|
|
194
|
+
expected = {}
|
|
195
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=True, reorderLists=True)
|
|
196
|
+
self.assertEqual(result, expected)
|
|
197
|
+
|
|
198
|
+
def test_single_empty_dictionary(self):
|
|
199
|
+
primus = {}
|
|
200
|
+
expected = {}
|
|
201
|
+
result = updateExtendPolishDictionaryLists(primus, destroyDuplicates=False, reorderLists=False)
|
|
202
|
+
self.assertEqual(result, expected)
|
|
203
|
+
|
|
204
|
+
def test_single_empty_dictionary_with_options(self):
|
|
205
|
+
primus = {}
|
|
206
|
+
expected = {}
|
|
207
|
+
result = updateExtendPolishDictionaryLists(primus, destroyDuplicates=True, reorderLists=True)
|
|
208
|
+
self.assertEqual(result, expected)
|
|
209
|
+
|
|
210
|
+
def test_single_dictionary_primus(self):
|
|
211
|
+
primus = {'a': [3, 1], 'b': [2]}
|
|
212
|
+
expected = {'a': [3, 1], 'b': [2]}
|
|
213
|
+
result = updateExtendPolishDictionaryLists(primus, destroyDuplicates=False, reorderLists=False)
|
|
214
|
+
self.assertEqual(result, expected)
|
|
215
|
+
|
|
216
|
+
def test_single_dictionary_primus_ignore_list_ordering_variant(self):
|
|
217
|
+
primus = {'a': [3, 1], 'b': [2]}
|
|
218
|
+
expected = {'a': [1, 3], 'b': [2]}
|
|
219
|
+
result = updateExtendPolishDictionaryLists(primus, destroyDuplicates=False, reorderLists=True)
|
|
220
|
+
self.assertEqual(result, expected)
|
|
221
|
+
|
|
222
|
+
def test_single_dictionary_primus_destroy_duplicates_ignore_list_ordering_variant(self):
|
|
223
|
+
primus = {'a': [3, 1, 3], 'b': [2, 2]}
|
|
224
|
+
expected = {'a': [3, 1], 'b': [2]}
|
|
225
|
+
result = updateExtendPolishDictionaryLists(primus, destroyDuplicates=True, reorderLists=False)
|
|
226
|
+
self.assertEqual(result, expected)
|
|
227
|
+
|
|
228
|
+
def test_single_dictionary_primus_destroy_duplicates_ignore_list_ordering(self):
|
|
229
|
+
primus = {'a': [3, 1, 3], 'b': [2, 2]}
|
|
230
|
+
expected = {'a': [1, 3], 'b': [2]}
|
|
231
|
+
result = updateExtendPolishDictionaryLists(primus, destroyDuplicates=True, reorderLists=True)
|
|
232
|
+
self.assertEqual(result, expected)
|
|
233
|
+
|
|
234
|
+
def test_single_dictionary_secundus(self):
|
|
235
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
236
|
+
expected = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
237
|
+
result = updateExtendPolishDictionaryLists(secundus, destroyDuplicates=False, reorderLists=False)
|
|
238
|
+
self.assertEqual(result, expected)
|
|
239
|
+
|
|
240
|
+
def test_single_dictionary_secundus_ignore_list_ordering(self):
|
|
241
|
+
secundus = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
242
|
+
expected = {'a': [1, 3, 6, 9, 22], 'b': [2, 3, 111111]}
|
|
243
|
+
result = updateExtendPolishDictionaryLists(secundus, destroyDuplicates=False, reorderLists=True)
|
|
244
|
+
self.assertEqual(result, expected)
|
|
245
|
+
|
|
246
|
+
def test_single_dictionary_secundus_destroy_duplicates(self):
|
|
247
|
+
secundus = {'a': [9, 6, 1, 22, 3, 9], 'b': [111111, 2, 3, 2]}
|
|
248
|
+
expected = {'a': [9, 6, 1, 22, 3], 'b': [111111, 2, 3]}
|
|
249
|
+
result = updateExtendPolishDictionaryLists(secundus, destroyDuplicates=True, reorderLists=False)
|
|
250
|
+
self.assertEqual(result, expected)
|
|
251
|
+
|
|
252
|
+
def test_single_dictionary_secundus_destroy_duplicates_ignore_list_ordering(self):
|
|
253
|
+
secundus = {'a': [9, 6, 1, 22, 3, 9], 'b': [111111, 2, 3, 2]}
|
|
254
|
+
expected = {'a': [1, 3, 6, 9, 22], 'b': [2, 3, 111111]}
|
|
255
|
+
result = updateExtendPolishDictionaryLists(secundus, destroyDuplicates=True, reorderLists=True)
|
|
256
|
+
self.assertEqual(result, expected)
|
|
257
|
+
|
|
258
|
+
def test_with_sets(self):
|
|
259
|
+
primus = {'a': {3, 1}, 'b': {2}}
|
|
260
|
+
secundus = {'a': {9, 6, 1, 22, 3}, 'b': {111111, 2, 3}}
|
|
261
|
+
expected = {'a': [3, 1, 9, 6, 22], 'b': [2, 111111, 3]}
|
|
262
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=True, reorderLists=False) # type: ignore
|
|
263
|
+
self.assertCountEqual(result['a'], expected['a'])
|
|
264
|
+
|
|
265
|
+
def test_with_tuples(self):
|
|
266
|
+
primus = {'a': (3, 1), 'b': (2,)}
|
|
267
|
+
secundus = {'a': (9, 6, 1, 22, 3), 'b': (111111, 2, 3)}
|
|
268
|
+
expected = {'a': [3, 1, 9, 6, 1, 22, 3], 'b': [2, 111111, 2, 3]}
|
|
269
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=False)
|
|
270
|
+
self.assertEqual(result, expected)
|
|
271
|
+
|
|
272
|
+
def test_with_ndarray(self):
|
|
273
|
+
primus = {'a': numpy.array([3, 1]), 'b': numpy.array([2])}
|
|
274
|
+
secundus = {'a': numpy.array([9, 6, 1, 22, 3]), 'b': numpy.array([111111, 2, 3])}
|
|
275
|
+
expected = {'a': [3, 1, 9, 6, 1, 22, 3], 'b': [2, 111111, 2, 3]}
|
|
276
|
+
result = updateExtendPolishDictionaryLists(primus, secundus, destroyDuplicates=False, reorderLists=False) # type: ignore
|
|
277
|
+
self.assertEqual(result, expected)
|
|
278
|
+
|
|
279
|
+
def test_empty_input(self):
|
|
280
|
+
self.assertEqual(stringItUp(), [])
|
|
281
|
+
|
|
282
|
+
def test_single_string(self):
|
|
283
|
+
self.assertEqual(stringItUp("hello"), ["hello"])
|
|
284
|
+
|
|
285
|
+
def test_single_integer(self):
|
|
286
|
+
self.assertEqual(stringItUp(123), ["123"])
|
|
287
|
+
|
|
288
|
+
def test_single_float(self):
|
|
289
|
+
self.assertEqual(stringItUp(3.14), ["3.14"])
|
|
290
|
+
|
|
291
|
+
def test_single_list(self):
|
|
292
|
+
self.assertCountEqual(stringItUp([1, 2, "three"]), ["1", "2", "three"])
|
|
293
|
+
|
|
294
|
+
def test_single_tuple(self):
|
|
295
|
+
self.assertCountEqual(stringItUp((1, 2, "three")), ["1", "2", "three"])
|
|
296
|
+
|
|
297
|
+
def test_single_set(self):
|
|
298
|
+
self.assertCountEqual(stringItUp({1, 2, "three"}), ["1", "2", "three"])
|
|
299
|
+
|
|
300
|
+
def test_single_dict(self):
|
|
301
|
+
self.assertCountEqual(stringItUp({"a": 1, "b": "two"}), ["a", "1", "b", "two"])
|
|
302
|
+
|
|
303
|
+
def test_nested_list(self):
|
|
304
|
+
self.assertCountEqual(stringItUp([1, [2, "three"], 4]), ["1", "2", "three", "4"])
|
|
305
|
+
|
|
306
|
+
def test_nested_tuple(self):
|
|
307
|
+
self.assertCountEqual(stringItUp((1, (2, "three"), 4)), ["1", "2", "three", "4"])
|
|
308
|
+
|
|
309
|
+
def test_nested_set(self):
|
|
310
|
+
self.assertCountEqual(stringItUp({1, frozenset({2, "three"}), 4}), ["1", "2", "three", "4"])
|
|
311
|
+
|
|
312
|
+
def test_nested_dict(self):
|
|
313
|
+
self.assertCountEqual(stringItUp({"a": 1, "b": {"c": 2, "d": "three"}}), ["a", "1", "b", "c", "2", "d", "three"])
|
|
314
|
+
|
|
315
|
+
def test_mixed_data_types(self):
|
|
316
|
+
self.assertCountEqual(stringItUp(1, "two", [3, "four"], {"five": 5}), ["1", "two", "3", "four", "five", "5"])
|
|
317
|
+
|
|
318
|
+
def test_with_numpy_array(self):
|
|
319
|
+
self.assertCountEqual(stringItUp(numpy.array([1, 2, 3])), ["1", "2", "3"])
|
|
320
|
+
|
|
321
|
+
def test_with_custom_object(self):
|
|
322
|
+
class MyObject:
|
|
323
|
+
def __str__(self):
|
|
324
|
+
return "MyObject"
|
|
325
|
+
self.assertEqual(stringItUp(MyObject()), ["MyObject"])
|
|
326
|
+
|
|
327
|
+
def test_with_none(self):
|
|
328
|
+
self.assertEqual(stringItUp(None), ["None"])
|
|
329
|
+
|
|
330
|
+
def test_with_boolean(self):
|
|
331
|
+
self.assertCountEqual(stringItUp(True, False), ["True", "False"])
|
|
332
|
+
|
|
333
|
+
def test_with_complex_number(self):
|
|
334
|
+
self.assertEqual(stringItUp(1+2j), ["(1+2j)"])
|
|
335
|
+
|
|
336
|
+
def test_with_bytes(self):
|
|
337
|
+
self.assertEqual(stringItUp(b"bytes"), ["b'bytes'"])
|
|
338
|
+
|
|
339
|
+
if __name__ == '__main__':
|
|
340
|
+
unittest.main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from Z0Z_tools import readAudioFile, loadWaveforms
|
|
2
|
+
import numpy
|
|
3
|
+
import pathlib
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
class TestReadAudioFile(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def setUp(self):
|
|
9
|
+
self.test_data_dir = pathlib.Path("unittests/dataSamples")
|
|
10
|
+
self.mono_file = self.test_data_dir / "testWooWooMono16kHz32integerClipping9sec.wav"
|
|
11
|
+
self.stereo_file = self.test_data_dir / "testSine2ch5sec.wav"
|
|
12
|
+
self.non_audio_file = self.test_data_dir / "testVideo11sec.mkv"
|
|
13
|
+
|
|
14
|
+
def test_read_mono_audio_file(self):
|
|
15
|
+
waveform = readAudioFile(self.mono_file)
|
|
16
|
+
self.assertIsInstance(waveform, numpy.ndarray)
|
|
17
|
+
self.assertEqual(waveform.ndim, 2) # Mono should have 1 dimension
|
|
18
|
+
|
|
19
|
+
def test_read_stereo_audio_file(self):
|
|
20
|
+
waveform = readAudioFile(self.stereo_file)
|
|
21
|
+
self.assertIsInstance(waveform, numpy.ndarray)
|
|
22
|
+
self.assertEqual(waveform.ndim, 2) # Stereo should have 2 dimensions
|
|
23
|
+
self.assertEqual(waveform.shape[0], 2) # First dimension should be 2 for stereo
|
|
24
|
+
|
|
25
|
+
class TestLoadWaveforms(unittest.TestCase):
|
|
26
|
+
|
|
27
|
+
def test_load_waveforms_mono(self):
|
|
28
|
+
"""Test loading mono waveforms with different sample rates."""
|
|
29
|
+
path_filenames = [
|
|
30
|
+
pathlib.Path("unittests/dataSamples/testWooWooMono16kHz32integerClipping9secCopy1.wav"),
|
|
31
|
+
pathlib.Path("unittests/dataSamples/testWooWooMono16kHz32integerClipping9secCopy2.wav"),
|
|
32
|
+
pathlib.Path("unittests/dataSamples/testWooWooMono16kHz32integerClipping9secCopy3.wav"),
|
|
33
|
+
]
|
|
34
|
+
array_waveforms = loadWaveforms(path_filenames, sampleRate=44100)
|
|
35
|
+
self.assertEqual(array_waveforms.shape, (2, 396900, 3))
|
|
36
|
+
|
|
37
|
+
def test_load_waveforms_stereo(self):
|
|
38
|
+
"""Test loading stereo waveforms with different sample rates."""
|
|
39
|
+
path_filenames = [
|
|
40
|
+
pathlib.Path("unittests/dataSamples/testSine2ch5secCopy1.wav"),
|
|
41
|
+
pathlib.Path("unittests/dataSamples/testSine2ch5secCopy2.wav"),
|
|
42
|
+
pathlib.Path("unittests/dataSamples/testSine2ch5secCopy3.wav"),
|
|
43
|
+
pathlib.Path("unittests/dataSamples/testSine2ch5secCopy4.wav"),
|
|
44
|
+
]
|
|
45
|
+
array_waveforms = loadWaveforms(path_filenames, sampleRate=44100)
|
|
46
|
+
self.assertEqual(array_waveforms.shape, (2, 220500, 4))
|
|
47
|
+
|
|
48
|
+
def test_load_waveforms_mixed_channels(self):
|
|
49
|
+
"""Test loading a mix of mono and stereo waveforms."""
|
|
50
|
+
path_filenames = [
|
|
51
|
+
pathlib.Path("unittests/dataSamples/testWooWooMono16kHz32integerClipping9sec.wav"),
|
|
52
|
+
pathlib.Path("unittests/dataSamples/testSine2ch5sec.wav")
|
|
53
|
+
]
|
|
54
|
+
array_waveforms = loadWaveforms(path_filenames, sampleRate=44100)
|
|
55
|
+
self.assertEqual(array_waveforms.shape, (2, 396900, 2))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
unittest.main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
from Z0Z_tools import pipAnything
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
class TestPipAnything(unittest.TestCase):
|
|
8
|
+
|
|
9
|
+
def test_makeListRequirementsFromRequirementsFile(self):
|
|
10
|
+
"""
|
|
11
|
+
Test the makeListRequirementsFromRequirementsFile function.
|
|
12
|
+
"""
|
|
13
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
14
|
+
# Create a temporary requirements file
|
|
15
|
+
requirements_file = Path(tempdir) / 'requirements.txt'
|
|
16
|
+
requirements_content = """
|
|
17
|
+
# This is a comment
|
|
18
|
+
package-A==1.2.3
|
|
19
|
+
package-B>=4.5.6,<=7.8.9
|
|
20
|
+
package_C
|
|
21
|
+
# Another comment
|
|
22
|
+
analyzeAudio@git+https://github.com/hunterhogan/analyzeAudio.git
|
|
23
|
+
"""
|
|
24
|
+
requirements_file.write_text(requirements_content)
|
|
25
|
+
|
|
26
|
+
# Test with a single file
|
|
27
|
+
requirements = pipAnything.makeListRequirementsFromRequirementsFile(requirements_file)
|
|
28
|
+
self.assertEqual(len(requirements), 4)
|
|
29
|
+
self.assertIn('package-A==1.2.3', requirements)
|
|
30
|
+
self.assertIn('package-B>=4.5.6,<=7.8.9', requirements)
|
|
31
|
+
self.assertIn('package_C', requirements)
|
|
32
|
+
self.assertIn('analyzeAudio@git+https://github.com/hunterhogan/analyzeAudio.git', requirements)
|
|
33
|
+
|
|
34
|
+
# Test with multiple files
|
|
35
|
+
requirements2 = pipAnything.makeListRequirementsFromRequirementsFile(requirements_file, requirements_file)
|
|
36
|
+
self.assertEqual(len(requirements2), 4) # Should still be 4, duplicates removed
|
|
37
|
+
|
|
38
|
+
# Test with non-existent file
|
|
39
|
+
nonexistent_file = Path(tempdir) / 'nonexistent.txt'
|
|
40
|
+
requirements3 = pipAnything.makeListRequirementsFromRequirementsFile(nonexistent_file)
|
|
41
|
+
self.assertEqual(len(requirements3), 0) # Should be empty
|
|
42
|
+
|
|
43
|
+
def test_make_setupDOTpy(self):
|
|
44
|
+
"""
|
|
45
|
+
Test the make_setupDOTpy function.
|
|
46
|
+
"""
|
|
47
|
+
relative_path_package = 'my_package'
|
|
48
|
+
list_requirements = ['numpy', 'pandas']
|
|
49
|
+
setup_content = pipAnything.make_setupDOTpy(relative_path_package, list_requirements)
|
|
50
|
+
|
|
51
|
+
# Check if the generated content contains expected elements
|
|
52
|
+
self.assertIn(f"name='{Path(relative_path_package).name}'", setup_content)
|
|
53
|
+
self.assertIn(f"packages=find_packages(where=r'{relative_path_package}')", setup_content)
|
|
54
|
+
self.assertIn(f"package_dir={{'': r'{relative_path_package}'}}", setup_content)
|
|
55
|
+
self.assertIn(f"install_requires={list_requirements},", setup_content)
|
|
56
|
+
self.assertIn("include_package_data=True", setup_content)
|
|
57
|
+
|
|
58
|
+
if __name__ == '__main__':
|
|
59
|
+
unittest.main()
|