cedbox 0.1.0__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.
- cedbox/__init__.py +13 -0
- cedbox/easymorse.py +80 -0
- cedbox/easywav.py +41 -0
- cedbox/inputs.py +120 -0
- cedbox/yggdrasil.py +223 -0
- cedbox-0.1.0.dist-info/METADATA +323 -0
- cedbox-0.1.0.dist-info/RECORD +10 -0
- cedbox-0.1.0.dist-info/WHEEL +5 -0
- cedbox-0.1.0.dist-info/licenses/LICENSE +21 -0
- cedbox-0.1.0.dist-info/top_level.txt +1 -0
cedbox/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CedBox - A Python utility package for data handling, input validation, Morse code processing, and audio generation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from cedbox.yggdrasil import Yggdrasil
|
|
6
|
+
from cedbox.inputs import (
|
|
7
|
+
string_put, int_put, float_put, choice_put,
|
|
8
|
+
bool_put, file_put, date_put, mail_put
|
|
9
|
+
)
|
|
10
|
+
from cedbox.easymorse import EasyMorse, MORSE_CODE_DICT
|
|
11
|
+
from cedbox.easywav import EasyWav
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
cedbox/easymorse.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
MORSE_CODE_DICT = {
|
|
2
|
+
'A': '.-', 'B': '-...',
|
|
3
|
+
'C': '-.-.', 'D': '-..', 'E': '.',
|
|
4
|
+
'F': '..-.', 'G': '--.', 'H': '....',
|
|
5
|
+
'I': '..', 'J': '.---', 'K': '-.-',
|
|
6
|
+
'L': '.-..', 'M': '--', 'N': '-.',
|
|
7
|
+
'O': '---', 'P': '.--.', 'Q': '--.-',
|
|
8
|
+
'R': '.-.', 'S': '...', 'T': '-',
|
|
9
|
+
'U': '..-', 'V': '...-', 'W': '.--',
|
|
10
|
+
'X': '-..-', 'Y': '-.--', 'Z': '--..',
|
|
11
|
+
'1': '.----', '2': '..---', '3': '...--',
|
|
12
|
+
'4': '....-', '5': '.....', '6': '-....',
|
|
13
|
+
'7': '--...', '8': '---..', '9': '----.',
|
|
14
|
+
'0': '-----', ', ': '--..--', '.': '.-.-.-',
|
|
15
|
+
'?': '..--..', '/': '-..-.', '-': '-....-',
|
|
16
|
+
'(': '-.--.', ')': '-.--.-', ':': '---...',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EasyMorse:
|
|
21
|
+
"""Class for creating and managing Morse code time units."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, text, morse_dict=None, prefix:bool|str=False):
|
|
24
|
+
"""
|
|
25
|
+
Initialize the MorseCodeTimes class.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
morse_dict = morse_dict if morse_dict is not None else MORSE_CODE_DICT
|
|
31
|
+
morse_dict.update({' ': '_____'})
|
|
32
|
+
self.morse_dict = morse_dict
|
|
33
|
+
self.times_dict = self._create_times_dict()
|
|
34
|
+
|
|
35
|
+
if isinstance(prefix, bool):
|
|
36
|
+
self.prefix = "._._._-___._._._-___._._._-___-_._._._._-_____" if prefix else "" #VVV- as prefix
|
|
37
|
+
elif isinstance(prefix, str):
|
|
38
|
+
self.prefix = prefix
|
|
39
|
+
|
|
40
|
+
self.raw_text = text
|
|
41
|
+
self.text_to_morse = None
|
|
42
|
+
self.morse_code = None
|
|
43
|
+
self.morse_code_times = None
|
|
44
|
+
self.morse_message = None
|
|
45
|
+
self.morse_seq = None
|
|
46
|
+
self.set_text(text)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _create_times_dict(self):
|
|
50
|
+
"""
|
|
51
|
+
Convert Morse code dictionary to time units dictionary.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
dict: Dictionary mapping characters to tuples of time units.
|
|
55
|
+
"""
|
|
56
|
+
times_dict = {
|
|
57
|
+
char: tuple(1 if c == '.' else 3 for c in code)
|
|
58
|
+
for char, code in self.morse_dict.items()
|
|
59
|
+
}
|
|
60
|
+
times_dict.update({' ': (-5,)})
|
|
61
|
+
return times_dict
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_text(self, raw_text: str):
|
|
65
|
+
|
|
66
|
+
self.raw_text = raw_text
|
|
67
|
+
|
|
68
|
+
self.text_to_morse = [char for char in self.raw_text.upper() if char in self.morse_dict]
|
|
69
|
+
|
|
70
|
+
morse_code = ['_'.join(self.morse_dict[char]) for char in self.text_to_morse]
|
|
71
|
+
|
|
72
|
+
self.morse_code = '___'.join(morse_code).replace('_______________', '_____')
|
|
73
|
+
|
|
74
|
+
self.morse_message = self.prefix + self.morse_code if self.prefix else self.morse_code
|
|
75
|
+
|
|
76
|
+
self.morse_seq = [{'.': 1, '-': 3, '_': -3}[char] for char in self.morse_message]
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
morse = EasyMorse('KM km')
|
|
80
|
+
print(morse.morse_seq)
|
cedbox/easywav.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import wave
|
|
2
|
+
import math
|
|
3
|
+
import struct
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EasyWav:
|
|
7
|
+
def __init__(self, sequence: list[int], sample_rate=44100, frequency=440, time_unit=100):
|
|
8
|
+
self.audio_samples: list[int] = []
|
|
9
|
+
self.sample_rate: int = sample_rate
|
|
10
|
+
self.frequency: int = frequency
|
|
11
|
+
sequence: list[int] = [time_unit*seq for seq in sequence]
|
|
12
|
+
self.sequence: list[int] = sequence
|
|
13
|
+
self.from_seq(sequence)
|
|
14
|
+
|
|
15
|
+
def _generate_samples(self, duration) -> list[int]:
|
|
16
|
+
if duration > 0: # Signal
|
|
17
|
+
num_samples = int(abs(duration) * self.sample_rate / 1000)
|
|
18
|
+
return [int(32767 * math.sin(2 * math.pi * self.frequency * (float(i) / self.sample_rate)))
|
|
19
|
+
for i in range(num_samples)]
|
|
20
|
+
else: # Pause
|
|
21
|
+
num_samples = int(abs(duration) * self.sample_rate / 1000)
|
|
22
|
+
return [0] * num_samples
|
|
23
|
+
|
|
24
|
+
def from_seq(self, durations) -> None:
|
|
25
|
+
audio_samples = []
|
|
26
|
+
for duration in durations:
|
|
27
|
+
audio_samples.extend(self._generate_samples(duration))
|
|
28
|
+
self.audio_samples = audio_samples
|
|
29
|
+
|
|
30
|
+
def save(self, filename='output.wav') -> None:
|
|
31
|
+
with wave.open(filename, 'w') as wave_file:
|
|
32
|
+
wave_file.setnchannels(1)
|
|
33
|
+
wave_file.setsampwidth(2)
|
|
34
|
+
wave_file.setframerate(self.sample_rate)
|
|
35
|
+
wave_file.writeframes(struct.pack('h' * len(self.audio_samples), *self.audio_samples))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == '__main__':
|
|
39
|
+
data = [1, -1, 3, 1, -1, 3]
|
|
40
|
+
wav = EasyWav(sequence=data)
|
|
41
|
+
wav.save('output.wav')
|
cedbox/inputs.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import pathlib
|
|
3
|
+
import re
|
|
4
|
+
import email.utils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def string_put(text, max_times=None, times=1, default='') -> str:
|
|
8
|
+
raw_input = input(text)
|
|
9
|
+
if max_times and times == max_times:
|
|
10
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
11
|
+
return default
|
|
12
|
+
else:
|
|
13
|
+
return raw_input
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def int_put(text, max_times=None, times=1, default=1, conditions=None) -> int:
|
|
17
|
+
if conditions is None:
|
|
18
|
+
conditions = []
|
|
19
|
+
|
|
20
|
+
raw_input = input(text)
|
|
21
|
+
try:
|
|
22
|
+
int_input = int(raw_input)
|
|
23
|
+
for condition in conditions:
|
|
24
|
+
assert condition(int_input)
|
|
25
|
+
return int_input
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(f'{raw_input} is not valid {e}')
|
|
28
|
+
if max_times and times >= max_times:
|
|
29
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
30
|
+
return default
|
|
31
|
+
return int_put(text, max_times=max_times, times=times + 1, default=default, conditions=conditions)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def float_put(text, max_times=None, times=1, default=1.0, conditions=None) -> float:
|
|
35
|
+
if conditions is None:
|
|
36
|
+
conditions = []
|
|
37
|
+
|
|
38
|
+
raw_input = input(text)
|
|
39
|
+
try:
|
|
40
|
+
float_input = float(raw_input)
|
|
41
|
+
for condition in conditions:
|
|
42
|
+
assert condition(float_input)
|
|
43
|
+
return float_input
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f'{raw_input} is not valid {e}')
|
|
46
|
+
if max_times and times >= max_times:
|
|
47
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
48
|
+
return default
|
|
49
|
+
return float_put(text, max_times=max_times, times=times + 1, default=default, conditions=conditions)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def choice_put(text: str, choices: list, max_times=None, times=1, def_choice_by_index=1) -> str:
|
|
53
|
+
raw_input = input(text + f"({'/'.join(choices)})" + ': ')
|
|
54
|
+
|
|
55
|
+
if raw_input in choices:
|
|
56
|
+
return raw_input
|
|
57
|
+
else:
|
|
58
|
+
if max_times and times >= max_times:
|
|
59
|
+
default = choices[def_choice_by_index]
|
|
60
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
61
|
+
return default
|
|
62
|
+
print(f'{raw_input} is not valid')
|
|
63
|
+
return choice_put(text, choices, max_times=max_times, times=times + 1, def_choice_by_index=def_choice_by_index)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def bool_put(text: str, max_times=None, times=1, default=False) -> bool:
|
|
67
|
+
raw_input = choice_put(text, choices=['y', 'n'])
|
|
68
|
+
|
|
69
|
+
match raw_input:
|
|
70
|
+
case 'y':
|
|
71
|
+
return True
|
|
72
|
+
case 'n':
|
|
73
|
+
return False
|
|
74
|
+
case _:
|
|
75
|
+
|
|
76
|
+
print(f'{raw_input} is not valid')
|
|
77
|
+
return bool_put(text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def file_put(text, max_times=None, times=1, default=None):
|
|
81
|
+
raw_input = input(text).strip()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
path = pathlib.Path(raw_input)
|
|
85
|
+
assert path.exists()
|
|
86
|
+
return path
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f'{raw_input} is not a valid Path: {e}')
|
|
89
|
+
if max_times and times >= max_times:
|
|
90
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
91
|
+
return default
|
|
92
|
+
return file_put(text, max_times=max_times, times=times + 1, default=default)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def date_put(text, max_times=None, times=1, default=None):
|
|
96
|
+
date_raw = input(f'{text}(YYYY-MM-DD)')
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
date = datetime.strptime(date_raw, '%Y-%m-%d')
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f'{date_raw} is not valid', e)
|
|
102
|
+
if max_times and times >= max_times:
|
|
103
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
104
|
+
return default
|
|
105
|
+
return date_put(text, max_times=max_times, times=times + 1, default=default)
|
|
106
|
+
return date
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def mail_put(text, max_times=None, times=1, default=None):
|
|
110
|
+
email_str = input(text)
|
|
111
|
+
try:
|
|
112
|
+
email_address = email.utils.parseaddr(email_str)[1]
|
|
113
|
+
assert re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email_address)
|
|
114
|
+
return email_address
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print(f'{email_str} is not valid', e)
|
|
117
|
+
if max_times and times >= max_times:
|
|
118
|
+
print(f'Exceeded {max_times} Trys, using default Value {default}')
|
|
119
|
+
return default
|
|
120
|
+
return mail_put(text, max_times=max_times, times=times + 1, default=default)
|
cedbox/yggdrasil.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import sqlite3
|
|
3
|
+
|
|
4
|
+
class Yggdrasil(dict):
|
|
5
|
+
def __init__(self, leaf_behavior='overwrite'):
|
|
6
|
+
"""
|
|
7
|
+
Initialize a new Yggdrasil tree.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
leaf_behavior (str or callable): How to handle duplicate leaf nodes.
|
|
11
|
+
If str, must be one of:
|
|
12
|
+
'overwrite': Replace existing value (default)
|
|
13
|
+
'append': Append to existing value if possible
|
|
14
|
+
'add': Add to existing value if both are numeric
|
|
15
|
+
'subtract': Subtract new value from existing value if both are numeric
|
|
16
|
+
'multiply': Multiply with existing value if both are numeric
|
|
17
|
+
'divide': Divide existing value by new value if both are numeric
|
|
18
|
+
If callable, must be a function that takes two arguments (existing_value, new_value)
|
|
19
|
+
and returns the value to be stored.
|
|
20
|
+
"""
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.leaf_behavior = leaf_behavior
|
|
23
|
+
|
|
24
|
+
def __getitem__(self, key):
|
|
25
|
+
if key not in self:
|
|
26
|
+
self[key] = self.__class__(leaf_behavior=self.leaf_behavior)
|
|
27
|
+
return super().__getitem__(key)
|
|
28
|
+
|
|
29
|
+
def __setitem__(self, key, values=None):
|
|
30
|
+
if not isinstance(values, list) or not values:
|
|
31
|
+
# Handle leaf node behavior if the key already exists
|
|
32
|
+
if key in self and not isinstance(super().__getitem__(key), Yggdrasil):
|
|
33
|
+
existing_value = super().__getitem__(key)
|
|
34
|
+
|
|
35
|
+
# Check if leaf_behavior is a callable (custom function)
|
|
36
|
+
if callable(self.leaf_behavior):
|
|
37
|
+
try:
|
|
38
|
+
# Call the custom function with existing and new values
|
|
39
|
+
new_value = self.leaf_behavior(existing_value, values)
|
|
40
|
+
super().__setitem__(key, new_value)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
# If the custom function fails, fall back to overwrite
|
|
43
|
+
super().__setitem__(key, values)
|
|
44
|
+
# Apply the specified behavior
|
|
45
|
+
elif self.leaf_behavior == 'overwrite' or existing_value is None:
|
|
46
|
+
# Default behavior - just overwrite
|
|
47
|
+
super().__setitem__(key, values)
|
|
48
|
+
elif self.leaf_behavior == 'append':
|
|
49
|
+
# Try to append values
|
|
50
|
+
try:
|
|
51
|
+
new_value = existing_value + values
|
|
52
|
+
super().__setitem__(key, new_value)
|
|
53
|
+
except (TypeError, ValueError):
|
|
54
|
+
# If append fails, fall back to overwrite
|
|
55
|
+
super().__setitem__(key, values)
|
|
56
|
+
elif self.leaf_behavior == 'add' and values is not None:
|
|
57
|
+
# Try to add values numerically - only for numeric types
|
|
58
|
+
if (isinstance(existing_value, (int, float)) and
|
|
59
|
+
isinstance(values, (int, float))):
|
|
60
|
+
new_value = existing_value + values
|
|
61
|
+
super().__setitem__(key, new_value)
|
|
62
|
+
else:
|
|
63
|
+
# For non-numeric types, fall back to overwrite
|
|
64
|
+
super().__setitem__(key, values)
|
|
65
|
+
elif self.leaf_behavior == 'multiply' and values is not None:
|
|
66
|
+
# Try to multiply values - only for numeric types
|
|
67
|
+
if (isinstance(existing_value, (int, float)) and
|
|
68
|
+
isinstance(values, (int, float))):
|
|
69
|
+
new_value = existing_value * values
|
|
70
|
+
super().__setitem__(key, new_value)
|
|
71
|
+
else:
|
|
72
|
+
# For non-numeric types, fall back to overwrite
|
|
73
|
+
super().__setitem__(key, values)
|
|
74
|
+
elif self.leaf_behavior == 'subtract' and values is not None:
|
|
75
|
+
# Try to subtract values - only for numeric types
|
|
76
|
+
if (isinstance(existing_value, (int, float)) and
|
|
77
|
+
isinstance(values, (int, float))):
|
|
78
|
+
new_value = existing_value - values
|
|
79
|
+
super().__setitem__(key, new_value)
|
|
80
|
+
else:
|
|
81
|
+
# For non-numeric types, fall back to overwrite
|
|
82
|
+
super().__setitem__(key, values)
|
|
83
|
+
elif self.leaf_behavior == 'divide' and values is not None:
|
|
84
|
+
# Try to divide values - only for numeric types
|
|
85
|
+
if (isinstance(existing_value, (int, float)) and
|
|
86
|
+
isinstance(values, (int, float))):
|
|
87
|
+
# Check for division by zero
|
|
88
|
+
if values == 0:
|
|
89
|
+
# For division by zero, fall back to overwrite
|
|
90
|
+
super().__setitem__(key, values)
|
|
91
|
+
else:
|
|
92
|
+
new_value = existing_value / values
|
|
93
|
+
super().__setitem__(key, new_value)
|
|
94
|
+
else:
|
|
95
|
+
# For non-numeric types, fall back to overwrite
|
|
96
|
+
super().__setitem__(key, values)
|
|
97
|
+
else:
|
|
98
|
+
# Unknown behavior or incompatible types, fall back to overwrite
|
|
99
|
+
super().__setitem__(key, values)
|
|
100
|
+
else:
|
|
101
|
+
# Key doesn't exist or is a Yggdrasil instance, just set the value
|
|
102
|
+
super().__setitem__(key, values)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
if key not in self:
|
|
106
|
+
super().__setitem__(key, self.__class__(leaf_behavior=self.leaf_behavior))
|
|
107
|
+
|
|
108
|
+
value = values.pop(0)
|
|
109
|
+
if values:
|
|
110
|
+
self[key].__setitem__(value, values)
|
|
111
|
+
else:
|
|
112
|
+
# When setting a leaf node, just set the value
|
|
113
|
+
# The behavior logic is already handled in the first part of this method
|
|
114
|
+
self[key] = value
|
|
115
|
+
|
|
116
|
+
def add_fiber(self, fiber):
|
|
117
|
+
"""
|
|
118
|
+
Add a fiber (path) to the tree.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
fiber: A list-like object or pandas Series representing a path in the tree.
|
|
122
|
+
The first element is the root node, and subsequent elements form the path.
|
|
123
|
+
"""
|
|
124
|
+
# Convert pandas Series to list if necessary
|
|
125
|
+
if isinstance(fiber, pd.Series):
|
|
126
|
+
fiber = fiber.tolist()
|
|
127
|
+
|
|
128
|
+
# Make a copy to avoid modifying the original
|
|
129
|
+
fiber_copy = fiber.copy() if hasattr(fiber, 'copy') else list(fiber)
|
|
130
|
+
|
|
131
|
+
sprout = fiber_copy.pop(0)
|
|
132
|
+
self[sprout] = fiber_copy
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_dataframe(cls, df, leaf_behavior='overwrite'):
|
|
136
|
+
"""
|
|
137
|
+
Create a new Yggdrasil tree from a pandas DataFrame.
|
|
138
|
+
|
|
139
|
+
Each row in the DataFrame will be added as a fiber to the tree.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
df (pandas.DataFrame): The DataFrame to convert to a tree
|
|
143
|
+
leaf_behavior (str or callable): How to handle duplicate leaf nodes
|
|
144
|
+
(passed to Yggdrasil constructor)
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Yggdrasil: A new Yggdrasil tree containing the data from the DataFrame
|
|
148
|
+
"""
|
|
149
|
+
tree = cls(leaf_behavior=leaf_behavior)
|
|
150
|
+
|
|
151
|
+
# Iterate through each row in the DataFrame
|
|
152
|
+
for _, row in df.iterrows():
|
|
153
|
+
# Add the row as a fiber to the tree
|
|
154
|
+
tree.add_fiber(row)
|
|
155
|
+
|
|
156
|
+
return tree
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def from_sql(cls, query, connection, leaf_behavior='overwrite'):
|
|
160
|
+
"""
|
|
161
|
+
Create a new Yggdrasil tree from a SQL query.
|
|
162
|
+
|
|
163
|
+
Executes the query and converts the result to a DataFrame,
|
|
164
|
+
then creates a tree from the DataFrame.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
query (str): The SQL query to execute
|
|
168
|
+
connection: A database connection object (sqlite3.Connection,
|
|
169
|
+
psycopg2.connection, etc.) or a connection string
|
|
170
|
+
leaf_behavior (str or callable): How to handle duplicate leaf nodes
|
|
171
|
+
(passed to Yggdrasil constructor)
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Yggdrasil: A new Yggdrasil tree containing the data from the query result
|
|
175
|
+
"""
|
|
176
|
+
# Handle different types of connections
|
|
177
|
+
if isinstance(connection, str):
|
|
178
|
+
# Assume it's a SQLite connection string
|
|
179
|
+
conn = sqlite3.connect(connection)
|
|
180
|
+
df = pd.read_sql_query(query, conn)
|
|
181
|
+
conn.close()
|
|
182
|
+
else:
|
|
183
|
+
# Assume it's an existing connection object
|
|
184
|
+
df = pd.read_sql_query(query, connection)
|
|
185
|
+
|
|
186
|
+
# Create a tree from the DataFrame
|
|
187
|
+
return cls.from_dataframe(df, leaf_behavior=leaf_behavior)
|
|
188
|
+
|
|
189
|
+
def print_tree(self, prefix="", is_root=True):
|
|
190
|
+
"""
|
|
191
|
+
Print the tree structure like a directory tree with lines.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
prefix (str): Prefix to use for the current line (for indentation)
|
|
195
|
+
is_root (bool): Whether this is the root of the tree
|
|
196
|
+
"""
|
|
197
|
+
# Get all keys in the current level
|
|
198
|
+
keys = list(self.keys())
|
|
199
|
+
|
|
200
|
+
if is_root and not keys:
|
|
201
|
+
print("Empty tree")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
# Process each key in the current level
|
|
205
|
+
for i, key in enumerate(keys):
|
|
206
|
+
is_last = i == len(keys) - 1
|
|
207
|
+
connector = "└── " if is_last else "├── "
|
|
208
|
+
|
|
209
|
+
# Print the current key with the appropriate connector
|
|
210
|
+
print(f"{prefix}{connector}{key}")
|
|
211
|
+
|
|
212
|
+
# Get the value for this key
|
|
213
|
+
value = self[key]
|
|
214
|
+
|
|
215
|
+
# Determine the prefix for the next level
|
|
216
|
+
next_prefix = prefix + (" " if is_last else "│ ")
|
|
217
|
+
|
|
218
|
+
# If the value is another Yggdrasil instance, recursively print it
|
|
219
|
+
if isinstance(value, Yggdrasil):
|
|
220
|
+
value.print_tree(prefix=next_prefix, is_root=False)
|
|
221
|
+
# Otherwise, print the value as a leaf node
|
|
222
|
+
elif value is not None:
|
|
223
|
+
print(f"{next_prefix}└── {value}")
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cedbox
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python utility package for data handling, input validation, Morse code processing, and audio generation
|
|
5
|
+
Author: CedBox Authors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Cedric Sascha Wagner
|
|
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
|
+
Classifier: Programming Language :: Python :: 3
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
29
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: pandas>=2.0.0
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# CedBox
|
|
40
|
+
|
|
41
|
+
CedBox is a Python utility package that provides various tools for data handling, user input validation, Morse code processing, and audio generation.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- **Yggdrasil**: A hierarchical tree-like data structure that extends Python's dictionary, with support for:
|
|
46
|
+
- Automatic node creation
|
|
47
|
+
- Loading data from DataFrames and SQL queries
|
|
48
|
+
- Tree visualization
|
|
49
|
+
|
|
50
|
+
- **Input Utilities**: Functions for handling user input with validation and type conversion:
|
|
51
|
+
- String input
|
|
52
|
+
- Integer input with validation
|
|
53
|
+
- Float input with validation
|
|
54
|
+
- Choice selection
|
|
55
|
+
- Boolean (yes/no) input
|
|
56
|
+
- File path input with validation
|
|
57
|
+
- Date input with validation
|
|
58
|
+
- Email input with validation
|
|
59
|
+
|
|
60
|
+
- **Morse Code Processing**: Tools for working with Morse code:
|
|
61
|
+
- Convert text to Morse code
|
|
62
|
+
- Represent Morse code as time units
|
|
63
|
+
- Generate Morse code sequences
|
|
64
|
+
|
|
65
|
+
- **Audio Generation**: Utilities for creating WAV audio files:
|
|
66
|
+
- Generate audio signals from sequences of durations
|
|
67
|
+
- Create Morse code audio signals
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install cedbox
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Usage Examples
|
|
76
|
+
|
|
77
|
+
### Yggdrasil
|
|
78
|
+
|
|
79
|
+
Yggdrasil is a hierarchical tree-like data structure that extends Python's dictionary with additional functionality.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from cedbox import Yggdrasil
|
|
83
|
+
import pandas as pd
|
|
84
|
+
|
|
85
|
+
# Create a new tree
|
|
86
|
+
tree = Yggdrasil()
|
|
87
|
+
|
|
88
|
+
# Add data with automatic node creation
|
|
89
|
+
tree['users']['john']['email'] = 'john@example.com'
|
|
90
|
+
tree['users']['john']['age'] = 30
|
|
91
|
+
tree['users']['jane']['email'] = 'jane@example.com'
|
|
92
|
+
tree['users']['jane']['age'] = 28
|
|
93
|
+
|
|
94
|
+
# Print tree structure
|
|
95
|
+
tree.print_tree()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Output:**
|
|
99
|
+
```
|
|
100
|
+
Yggdrasil
|
|
101
|
+
├── users
|
|
102
|
+
│ ├── john
|
|
103
|
+
│ │ ├── email: john@example.com
|
|
104
|
+
│ │ └── age: 30
|
|
105
|
+
│ └── jane
|
|
106
|
+
│ ├── email: jane@example.com
|
|
107
|
+
│ └── age: 28
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Creating from DataFrame
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# Create from DataFrame
|
|
114
|
+
df = pd.DataFrame({
|
|
115
|
+
'name': ['John', 'Jane'],
|
|
116
|
+
'email': ['john@example.com', 'jane@example.com'],
|
|
117
|
+
'age': [30, 28]
|
|
118
|
+
})
|
|
119
|
+
tree_from_df = Yggdrasil.from_dataframe(df)
|
|
120
|
+
tree_from_df.print_tree()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Output:**
|
|
124
|
+
```
|
|
125
|
+
Yggdrasil
|
|
126
|
+
├── 0
|
|
127
|
+
│ ├── name: John
|
|
128
|
+
│ ├── email: john@example.com
|
|
129
|
+
│ └── age: 30
|
|
130
|
+
└── 1
|
|
131
|
+
├── name: Jane
|
|
132
|
+
├── email: jane@example.com
|
|
133
|
+
└── age: 28
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Creating from SQL Query
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import sqlite3
|
|
140
|
+
|
|
141
|
+
# Create a sample database
|
|
142
|
+
conn = sqlite3.connect(':memory:')
|
|
143
|
+
cursor = conn.cursor()
|
|
144
|
+
cursor.execute('CREATE TABLE users (name TEXT, email TEXT, age INTEGER)')
|
|
145
|
+
cursor.execute('INSERT INTO users VALUES ("John", "john@example.com", 30)')
|
|
146
|
+
cursor.execute('INSERT INTO users VALUES ("Jane", "jane@example.com", 28)')
|
|
147
|
+
conn.commit()
|
|
148
|
+
|
|
149
|
+
# Create tree from SQL query
|
|
150
|
+
tree_from_sql = Yggdrasil.from_sql("SELECT * FROM users", conn)
|
|
151
|
+
tree_from_sql.print_tree()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Output:**
|
|
155
|
+
```
|
|
156
|
+
Yggdrasil
|
|
157
|
+
├── 0
|
|
158
|
+
│ ├── name: John
|
|
159
|
+
│ ├── email: john@example.com
|
|
160
|
+
│ └── age: 30
|
|
161
|
+
└── 1
|
|
162
|
+
├── name: Jane
|
|
163
|
+
├── email: jane@example.com
|
|
164
|
+
└── age: 28
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Input Utilities
|
|
168
|
+
|
|
169
|
+
CedBox provides various functions for handling user input with validation and type conversion.
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from cedbox.inputs import (
|
|
173
|
+
string_put, int_put, float_put, choice_put,
|
|
174
|
+
bool_put, file_put, date_put, mail_put
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# String input
|
|
178
|
+
name = string_put("Enter your name: ")
|
|
179
|
+
# User enters: John
|
|
180
|
+
print(f"Hello, {name}!")
|
|
181
|
+
# Output: Hello, John!
|
|
182
|
+
|
|
183
|
+
# Integer input with validation
|
|
184
|
+
age = int_put(
|
|
185
|
+
"Enter your age: ",
|
|
186
|
+
conditions=[lambda x: x > 0, lambda x: x < 120],
|
|
187
|
+
max_times=3,
|
|
188
|
+
default=30
|
|
189
|
+
)
|
|
190
|
+
# User enters: -5
|
|
191
|
+
# Output: -5 is not valid assert lambda x: x > 0
|
|
192
|
+
# User enters: 25
|
|
193
|
+
print(f"You are {age} years old.")
|
|
194
|
+
# Output: You are 25 years old.
|
|
195
|
+
|
|
196
|
+
# Float input with validation
|
|
197
|
+
height = float_put(
|
|
198
|
+
"Enter your height in meters: ",
|
|
199
|
+
conditions=[lambda x: 0.5 < x < 2.5],
|
|
200
|
+
default=1.75
|
|
201
|
+
)
|
|
202
|
+
# User enters: 1.85
|
|
203
|
+
print(f"Your height is {height} meters.")
|
|
204
|
+
# Output: Your height is 1.85 meters.
|
|
205
|
+
|
|
206
|
+
# Choice selection
|
|
207
|
+
color = choice_put("Select a color ", choices=['red', 'green', 'blue'])
|
|
208
|
+
# Output: Select a color (red/green/blue):
|
|
209
|
+
# User enters: yellow
|
|
210
|
+
# Output: yellow is not valid
|
|
211
|
+
# User enters: red
|
|
212
|
+
print(f"You selected {color}.")
|
|
213
|
+
# Output: You selected red.
|
|
214
|
+
|
|
215
|
+
# Boolean input
|
|
216
|
+
confirm = bool_put("Confirm? ")
|
|
217
|
+
# Output: Confirm? (y/n):
|
|
218
|
+
# User enters: y
|
|
219
|
+
print(f"Confirmed: {confirm}")
|
|
220
|
+
# Output: Confirmed: True
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Morse Code Processing
|
|
224
|
+
|
|
225
|
+
The EasyMorse class provides tools for working with Morse code.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from cedbox import EasyMorse, MORSE_CODE_DICT
|
|
229
|
+
|
|
230
|
+
# Create Morse code from text
|
|
231
|
+
morse = EasyMorse("SOS")
|
|
232
|
+
print(f"Original text: {morse.raw_text}")
|
|
233
|
+
# Output: Original text: SOS
|
|
234
|
+
|
|
235
|
+
print(f"Morse code: {morse.morse_code}")
|
|
236
|
+
# Output: Morse code: ._._.____-_-_-___._._._
|
|
237
|
+
|
|
238
|
+
# View the time sequence (1=dot, 3=dash, -3=pause between symbols)
|
|
239
|
+
print(f"Time sequence: {morse.morse_seq}")
|
|
240
|
+
# Output: Time sequence: [1, 1, 1, -3, 3, 3, 3, -3, 1, 1, 1]
|
|
241
|
+
|
|
242
|
+
# Create with custom prefix (VVV- is a common Morse prefix)
|
|
243
|
+
morse_with_prefix = EasyMorse("HELLO", prefix=True)
|
|
244
|
+
print(f"With prefix: {morse_with_prefix.morse_message}")
|
|
245
|
+
# Output: With prefix: ._._._-___._._._-___._._._-___-_._._._._-_____._._._.___._____._____._._..___._..___._..___---
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Audio Generation
|
|
249
|
+
|
|
250
|
+
The EasyWav class allows you to create WAV audio files from sequences of durations.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from cedbox import EasyMorse, EasyWav
|
|
254
|
+
|
|
255
|
+
# Create Morse code sequence for "SOS"
|
|
256
|
+
morse = EasyMorse("SOS")
|
|
257
|
+
sequence = morse.morse_seq
|
|
258
|
+
print(f"Morse sequence: {sequence}")
|
|
259
|
+
# Output: Morse sequence: [1, 1, 1, -3, 3, 3, 3, -3, 1, 1, 1]
|
|
260
|
+
|
|
261
|
+
# Generate WAV file from sequence
|
|
262
|
+
wav = EasyWav(
|
|
263
|
+
sequence=sequence,
|
|
264
|
+
frequency=800, # 800 Hz tone
|
|
265
|
+
time_unit=100 # 100ms per unit
|
|
266
|
+
)
|
|
267
|
+
wav.save('morse_sos.wav')
|
|
268
|
+
print("Audio file created: morse_sos.wav")
|
|
269
|
+
# Output: Audio file created: morse_sos.wav
|
|
270
|
+
|
|
271
|
+
# Create a more complex audio pattern
|
|
272
|
+
custom_sequence = [1, -1, 3, -1, 1, -3, 5, -5] # Custom pattern of tones and pauses
|
|
273
|
+
wav = EasyWav(
|
|
274
|
+
sequence=custom_sequence,
|
|
275
|
+
frequency=440, # A4 note
|
|
276
|
+
sample_rate=48000 # Higher quality
|
|
277
|
+
)
|
|
278
|
+
wav.save('custom_pattern.wav')
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Complete Example: Morse Code Converter
|
|
282
|
+
|
|
283
|
+
Here's a complete example that combines multiple components to create a Morse code converter:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from cedbox import EasyMorse, EasyWav, string_put
|
|
287
|
+
|
|
288
|
+
def morse_converter():
|
|
289
|
+
# Get input text from user
|
|
290
|
+
text = string_put("Enter text to convert to Morse code: ")
|
|
291
|
+
|
|
292
|
+
# Convert to Morse code
|
|
293
|
+
morse = EasyMorse(text, prefix=True)
|
|
294
|
+
|
|
295
|
+
print(f"Text: {morse.raw_text}")
|
|
296
|
+
print(f"Morse code: {morse.morse_code}")
|
|
297
|
+
|
|
298
|
+
# Generate audio file
|
|
299
|
+
wav = EasyWav(
|
|
300
|
+
sequence=morse.morse_seq,
|
|
301
|
+
frequency=800,
|
|
302
|
+
time_unit=80 # Faster speed
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
filename = f"morse_{text.replace(' ', '_')}.wav"
|
|
306
|
+
wav.save(filename)
|
|
307
|
+
print(f"Audio saved to {filename}")
|
|
308
|
+
|
|
309
|
+
return morse, filename
|
|
310
|
+
|
|
311
|
+
# Run the converter
|
|
312
|
+
morse_result, audio_file = morse_converter()
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Dependencies
|
|
316
|
+
|
|
317
|
+
- pandas >= 2.0.0
|
|
318
|
+
- setuptools == 80.9.0
|
|
319
|
+
- pytest (for testing)
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
cedbox/__init__.py,sha256=SO6IsIVwnWzy0Jfd7YKUmFZ7JjH-wlqP9vw5HyNJRAs,400
|
|
2
|
+
cedbox/easymorse.py,sha256=s7L-T-IjCVVNvehusQPJemdoLdLX5XzQyV4AQsz2Tig,2597
|
|
3
|
+
cedbox/easywav.py,sha256=aghh8nxuCIqmOvT57amnV_xBSkC8ifDTv6NbVoNihs4,1534
|
|
4
|
+
cedbox/inputs.py,sha256=1e-fJn6cAEApnaygmkTwFlIyFtm0uGj9jZGNG-IVzEM,4157
|
|
5
|
+
cedbox/yggdrasil.py,sha256=hrIrxefewEZl_ob-NWZrsnEzGiwfmIv3tOtIzuN0Buo,9889
|
|
6
|
+
cedbox-0.1.0.dist-info/licenses/LICENSE,sha256=Oau7wCqSC3IKMHCKRO1vQnkFIfBQV5C8dv3_0cmssRY,1076
|
|
7
|
+
cedbox-0.1.0.dist-info/METADATA,sha256=70FzcHnRt_JrD1sIdEG3DGe5VL30NVY2rC1PSclIn5A,8936
|
|
8
|
+
cedbox-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
cedbox-0.1.0.dist-info/top_level.txt,sha256=sldRWrukvac7nL5qTyloLvUUuCQgUyCET8tXq8fKyxo,7
|
|
10
|
+
cedbox-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cedric Sascha Wagner
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cedbox
|