Loading src/pygarden/listz.py 0 → 100644 +76 −0 Original line number Diff line number Diff line """ Common list utils """ from collections.abc import Iterable as IterableABC from typing import Tuple, Type, Iterable, Any, TypeVar terminal_iter_types = (str, bytes, bytearray, memoryview) preservable_iter_types = (list, tuple, set) def flatten(items: Iterable, ignore_types: Tuple[Type, ...]=terminal_iter_types) -> Iterable: """ Flatten a nested sequence into a single iterable. If input items is a list, tuple, or set, the returned iterable will maintain that type. Otherwise, it's returned as a list. Dict has behavior: flatten({'a': 1, 'b': [2, 3]}) -> ['a', 1, 'b', 2, 3] Args: items (Iterable): The nested sequence to flatten. ignore_types (Tuple[Type, ...]): Types to ignore during flattening. Defaults to (str, bytes, bytearray, memoryview) Returns: T: A flattened version of the input sequence, of the same type as the input. Example: >>> flatten([1, [2, 3, [4, 5]], 6]) [1, 2, 3, 4, 5, 6] >>> flatten([1, [2, 3, ["abc", "b"]], (4, 5), b"hello"]) [1, 2, 3, 'abc', 'b', 4, 5, b'hello'] >>> flatten([1, [2, 3, ["abc", "b"]], b"hello"], ignore_types=(bytes,)) [1, 2, 3, 'a', 'b', 'c', 'b', 4, 5, b'hello'] """ def _flatten(_items: Iterable) -> Any: if isinstance(_items, terminal_iter_types) and len(_items) == 1: yield _items else: if hasattr(_items, "items"): _items = [[x, y] for x, y in _items.items()] for x in _items: if isinstance(x, IterableABC) and not isinstance(x, ignore_types): yield from _flatten(x) else: yield x if isinstance(items, terminal_iter_types): return items return_type = list if isinstance(items, preservable_iter_types): return_type = type(items) return return_type(_flatten(items)) def clean(items: Iterable, to_drop: Tuple = (None, "", [])) -> Iterable: # docstring """ Drop items from a sequence. Designed to drop "empty" values, but could be used to drop anything. If original type of sequence is a list, tuple, or set, the returned iterable will maintain that type. Otherwise, returned as a list. Args: items (Iterable): The sequence to drop empty items from. to_drop (Tuple): The items to drop from iterable. Defaults to (None, "", []) """ return_type = list if isinstance(items, preservable_iter_types): return_type = type(items) return return_type(x for x in items if x not in to_drop) src/pygarden/pass_gen.py 0 → 100644 +35 −0 Original line number Diff line number Diff line from coolname.data import config from coolname import RandomGenerator import random from faker import Faker fake = Faker() def cool_name_adj(): adj_config = {} load_in = {'adj_any'} while len(load_in) > 0: next_up = load_in.pop() if next_up not in adj_config: adj_config[next_up] = config[next_up] load_in.update(set(adj_config[next_up].get('lists', []))) return adj_config def generate_password(): generator_config = { 'all': { 'type': 'cartesian', 'lists': ['adj_any', 'plant'] } } | cool_name_adj() generator = RandomGenerator(generator_config) generator.generate() return password if __name__ == "__main__": password = generate_password() print(f"Your generated password is: {password}") tests/test_pygarden/test_listz.py 0 → 100644 +213 −0 Original line number Diff line number Diff line import pytest from typing import Type, Tuple, Iterable, Any from collections.abc import Iterable as IterableABC from pygarden.listz import flatten, clean class TestFlatten: @pytest.mark.parametrize("input_list", [ [], [[]], [[], [], []], ]) def test_empty_sequences(self, input_list): assert flatten(input_list) == [] def test_custom_ignore_types(self): input_list = [1, [2, 3, ["abc", "def"]], (4, 5)] expected = [1, 2, 3, "abc", "def", (4, 5)] assert flatten(input_list, ignore_types=(str, tuple)) == expected @pytest.mark.parametrize("input_sequence,expected_type", [ ((1, (2, 3, (4, 5)), 6), tuple), ([1, [2, 3, [4, 5]], 6], list), ]) def test_preserves_input_type(self, input_sequence, expected_type): result = flatten(input_sequence) assert isinstance(result, expected_type) assert all(x == y for x, y in zip(flatten([1, 2, 3, 4, 5, 6]), result)) def test_with_ignored(self): input_list = [1, "hello", ["world", ["nested", "strings"]]] expected = [1, "hello", "world", "nested", "strings"] assert flatten(input_list) == expected input_list = [1, b"hello", [b"world", [b"nested", b"bytes"]]] expected = [1, b"hello", b"world", b"nested", b"bytes"] assert flatten(input_list) == expected def test_with_mixed_iterables(self): input_list = [1, set([2, 3]), {4: 'a', 5: 'b'}, (6, 7)] result = flatten(input_list) assert result == [1, 2, 3, 4, 'a', 5, 'b', 6, 7] @pytest.mark.parametrize("invalid_input", [ None, 42, 3.14, ]) def test_invalid_inputs(self, invalid_input): with pytest.raises(TypeError): flatten(invalid_input) @pytest.fixture def nested_data(self): return { 'simple': ([1, [2, 3, [4, 5]], 6], [1, 2, 3, 4, 5, 6]), 'mixed': ([1, [2, 3, ["abc", "b"]], (4, 5), b"hello"], [1, 2, 3, "abc", "b", 4, 5, b"hello"]), 'deep': ([1, [2, [3, [4, [5]]]]], [1, 2, 3, 4, 5]), 'flat': ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]) } @pytest.mark.parametrize("input_list,expected,ignore_types", [ ( [1, ["abc", 2], 3], [1, 'a', 'b', 'c', 2, 3], (bytes,) ), ( [1, ["hello", [2, "world"]], 3], [1, 'h', 'e', 'l', 'l', 'o', 2, 'w', 'o', 'r', 'l', 'd', 3], (bytes,) ), ( ["abc", [1, 2], ["def", 3]], ['a', 'b', 'c', 1, 2, 'd', 'e', 'f', 3], () ), ( [1, ["αβγ", 2], 3], # Testing with Unicode characters [1, 'α', 'β', 'γ', 2, 3], (bytes,) ), ]) def test_string_flattening_without_str_in_ignore_types(self, input_list, expected, ignore_types): assert flatten(input_list, ignore_types=ignore_types) == expected def test_complex_string_flattening(self): input_list = [ 1, ["hello", [2, "world"]], b"bytes", # should be preserved as is ["abc", ["def", ["ghi"]]] ] expected = [ 1, 'h', 'e', 'l', 'l', 'o', 2, 'w', 'o', 'r', 'l', 'd', b"bytes", 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ] assert flatten(input_list, ignore_types=(bytes,)) == expected def test_with_fixture(self, nested_data): for input_list, result in nested_data.values(): assert flatten(input_list) == result def test_large_nested_structure(self): large_list = list(range(1000)) nested_large = [large_list] * 10 result = flatten(nested_large) assert len(result) == 10000 assert all(x == y for x, y in zip(list(range(1000)) * 10, result)) def test_dictionary_flattening(self): # Test basic dictionary input_dict = {'a': 1, 'b': 2, 'c': 3} assert flatten(input_dict) == ['a', 1, 'b', 2, 'c', 3] # flattens to keys and values # Test nested dictionary input_dict = {'a': {'b': 2, 'c': 3}, 'd': 4} assert flatten(input_dict) == ['a', 'b', 2, 'c', 3, 'd', 4] # Test dictionary with lists input_dict = {'a': [1, 2, 3], 'b': {'c': [4, 5]}} assert flatten(input_dict) == ['a', 1, 2, 3, 'b', 'c', 4, 5] class TestClean: @pytest.mark.parametrize("input_sequence,expected", [ ([], []), ([None], []), ([""], []), ([[]], []), ([None, "", []], []), ([1, None, 2, "", 3, [], 4], [1, 2, 3, 4]), ]) def test_default_cleaning(self, input_sequence, expected): """Test cleaning with default to_drop values""" assert clean(input_sequence) == expected @pytest.mark.parametrize("input_sequence,to_drop,expected", [ ([1, 2, 3, 4], (2, 4), [1, 3]), (["a", "b", "c"], ("a", "c"), ["b"]), ([1, "a", 2, "b"], (1, 2), ["a", "b"]), ([1.0, 2.0, 3.0], (2.0,), [1.0, 3.0]), ]) def test_custom_drop_values(self, input_sequence, to_drop, expected): """Test cleaning with custom to_drop values""" assert clean(input_sequence, to_drop) == expected @pytest.mark.parametrize("input_sequence,expected_type", [ ([1, None, 2], list), (tuple([1, None, 2]), tuple), (set([1, None, 2]), set), ]) def test_preserves_input_type(self, input_sequence, expected_type): """Test that the function preserves the input sequence type""" result = clean(input_sequence) assert isinstance(result, expected_type) def test_with_mixed_types(self): """Test cleaning with mixed type values""" input_sequence = [1, None, "hello", [], "", {"key": "value"}, 3.14] expected = [1, "hello", {"key": "value"}, 3.14] assert clean(input_sequence) == expected @pytest.mark.parametrize("invalid_input", [ None, 42, 3.14, ]) def test_invalid_inputs(self, invalid_input): """Test that non-iterable inputs raise TypeError""" with pytest.raises(TypeError): clean(invalid_input) def test_empty_to_drop(self): """Test cleaning with empty to_drop tuple""" input_sequence = [1, None, "", [], 2] assert clean(input_sequence, ()) == input_sequence def test_large_sequence(self): """Test cleaning with a large sequence""" large_list = list(range(1000)) large_list.extend([None] * 1000) result = clean(large_list) assert len(result) == 1000 assert all(isinstance(x, int) for x in result) def test_nested_structures(self): """Test cleaning with nested structures""" input_sequence = [ 1, [2, None], [], (3, ""), {4, None}, ] expected = [1, [2, None], (3, ""), {4, None}] assert clean(input_sequence) == expected def test_with_generator_input(self): """Test cleaning with generator input""" def gen(): yield from [1, None, 2, "", 3] result = clean(gen()) assert isinstance(result, list) assert result == [1, 2, 3] No newline at end of file Loading
src/pygarden/listz.py 0 → 100644 +76 −0 Original line number Diff line number Diff line """ Common list utils """ from collections.abc import Iterable as IterableABC from typing import Tuple, Type, Iterable, Any, TypeVar terminal_iter_types = (str, bytes, bytearray, memoryview) preservable_iter_types = (list, tuple, set) def flatten(items: Iterable, ignore_types: Tuple[Type, ...]=terminal_iter_types) -> Iterable: """ Flatten a nested sequence into a single iterable. If input items is a list, tuple, or set, the returned iterable will maintain that type. Otherwise, it's returned as a list. Dict has behavior: flatten({'a': 1, 'b': [2, 3]}) -> ['a', 1, 'b', 2, 3] Args: items (Iterable): The nested sequence to flatten. ignore_types (Tuple[Type, ...]): Types to ignore during flattening. Defaults to (str, bytes, bytearray, memoryview) Returns: T: A flattened version of the input sequence, of the same type as the input. Example: >>> flatten([1, [2, 3, [4, 5]], 6]) [1, 2, 3, 4, 5, 6] >>> flatten([1, [2, 3, ["abc", "b"]], (4, 5), b"hello"]) [1, 2, 3, 'abc', 'b', 4, 5, b'hello'] >>> flatten([1, [2, 3, ["abc", "b"]], b"hello"], ignore_types=(bytes,)) [1, 2, 3, 'a', 'b', 'c', 'b', 4, 5, b'hello'] """ def _flatten(_items: Iterable) -> Any: if isinstance(_items, terminal_iter_types) and len(_items) == 1: yield _items else: if hasattr(_items, "items"): _items = [[x, y] for x, y in _items.items()] for x in _items: if isinstance(x, IterableABC) and not isinstance(x, ignore_types): yield from _flatten(x) else: yield x if isinstance(items, terminal_iter_types): return items return_type = list if isinstance(items, preservable_iter_types): return_type = type(items) return return_type(_flatten(items)) def clean(items: Iterable, to_drop: Tuple = (None, "", [])) -> Iterable: # docstring """ Drop items from a sequence. Designed to drop "empty" values, but could be used to drop anything. If original type of sequence is a list, tuple, or set, the returned iterable will maintain that type. Otherwise, returned as a list. Args: items (Iterable): The sequence to drop empty items from. to_drop (Tuple): The items to drop from iterable. Defaults to (None, "", []) """ return_type = list if isinstance(items, preservable_iter_types): return_type = type(items) return return_type(x for x in items if x not in to_drop)
src/pygarden/pass_gen.py 0 → 100644 +35 −0 Original line number Diff line number Diff line from coolname.data import config from coolname import RandomGenerator import random from faker import Faker fake = Faker() def cool_name_adj(): adj_config = {} load_in = {'adj_any'} while len(load_in) > 0: next_up = load_in.pop() if next_up not in adj_config: adj_config[next_up] = config[next_up] load_in.update(set(adj_config[next_up].get('lists', []))) return adj_config def generate_password(): generator_config = { 'all': { 'type': 'cartesian', 'lists': ['adj_any', 'plant'] } } | cool_name_adj() generator = RandomGenerator(generator_config) generator.generate() return password if __name__ == "__main__": password = generate_password() print(f"Your generated password is: {password}")
tests/test_pygarden/test_listz.py 0 → 100644 +213 −0 Original line number Diff line number Diff line import pytest from typing import Type, Tuple, Iterable, Any from collections.abc import Iterable as IterableABC from pygarden.listz import flatten, clean class TestFlatten: @pytest.mark.parametrize("input_list", [ [], [[]], [[], [], []], ]) def test_empty_sequences(self, input_list): assert flatten(input_list) == [] def test_custom_ignore_types(self): input_list = [1, [2, 3, ["abc", "def"]], (4, 5)] expected = [1, 2, 3, "abc", "def", (4, 5)] assert flatten(input_list, ignore_types=(str, tuple)) == expected @pytest.mark.parametrize("input_sequence,expected_type", [ ((1, (2, 3, (4, 5)), 6), tuple), ([1, [2, 3, [4, 5]], 6], list), ]) def test_preserves_input_type(self, input_sequence, expected_type): result = flatten(input_sequence) assert isinstance(result, expected_type) assert all(x == y for x, y in zip(flatten([1, 2, 3, 4, 5, 6]), result)) def test_with_ignored(self): input_list = [1, "hello", ["world", ["nested", "strings"]]] expected = [1, "hello", "world", "nested", "strings"] assert flatten(input_list) == expected input_list = [1, b"hello", [b"world", [b"nested", b"bytes"]]] expected = [1, b"hello", b"world", b"nested", b"bytes"] assert flatten(input_list) == expected def test_with_mixed_iterables(self): input_list = [1, set([2, 3]), {4: 'a', 5: 'b'}, (6, 7)] result = flatten(input_list) assert result == [1, 2, 3, 4, 'a', 5, 'b', 6, 7] @pytest.mark.parametrize("invalid_input", [ None, 42, 3.14, ]) def test_invalid_inputs(self, invalid_input): with pytest.raises(TypeError): flatten(invalid_input) @pytest.fixture def nested_data(self): return { 'simple': ([1, [2, 3, [4, 5]], 6], [1, 2, 3, 4, 5, 6]), 'mixed': ([1, [2, 3, ["abc", "b"]], (4, 5), b"hello"], [1, 2, 3, "abc", "b", 4, 5, b"hello"]), 'deep': ([1, [2, [3, [4, [5]]]]], [1, 2, 3, 4, 5]), 'flat': ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]) } @pytest.mark.parametrize("input_list,expected,ignore_types", [ ( [1, ["abc", 2], 3], [1, 'a', 'b', 'c', 2, 3], (bytes,) ), ( [1, ["hello", [2, "world"]], 3], [1, 'h', 'e', 'l', 'l', 'o', 2, 'w', 'o', 'r', 'l', 'd', 3], (bytes,) ), ( ["abc", [1, 2], ["def", 3]], ['a', 'b', 'c', 1, 2, 'd', 'e', 'f', 3], () ), ( [1, ["αβγ", 2], 3], # Testing with Unicode characters [1, 'α', 'β', 'γ', 2, 3], (bytes,) ), ]) def test_string_flattening_without_str_in_ignore_types(self, input_list, expected, ignore_types): assert flatten(input_list, ignore_types=ignore_types) == expected def test_complex_string_flattening(self): input_list = [ 1, ["hello", [2, "world"]], b"bytes", # should be preserved as is ["abc", ["def", ["ghi"]]] ] expected = [ 1, 'h', 'e', 'l', 'l', 'o', 2, 'w', 'o', 'r', 'l', 'd', b"bytes", 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ] assert flatten(input_list, ignore_types=(bytes,)) == expected def test_with_fixture(self, nested_data): for input_list, result in nested_data.values(): assert flatten(input_list) == result def test_large_nested_structure(self): large_list = list(range(1000)) nested_large = [large_list] * 10 result = flatten(nested_large) assert len(result) == 10000 assert all(x == y for x, y in zip(list(range(1000)) * 10, result)) def test_dictionary_flattening(self): # Test basic dictionary input_dict = {'a': 1, 'b': 2, 'c': 3} assert flatten(input_dict) == ['a', 1, 'b', 2, 'c', 3] # flattens to keys and values # Test nested dictionary input_dict = {'a': {'b': 2, 'c': 3}, 'd': 4} assert flatten(input_dict) == ['a', 'b', 2, 'c', 3, 'd', 4] # Test dictionary with lists input_dict = {'a': [1, 2, 3], 'b': {'c': [4, 5]}} assert flatten(input_dict) == ['a', 1, 2, 3, 'b', 'c', 4, 5] class TestClean: @pytest.mark.parametrize("input_sequence,expected", [ ([], []), ([None], []), ([""], []), ([[]], []), ([None, "", []], []), ([1, None, 2, "", 3, [], 4], [1, 2, 3, 4]), ]) def test_default_cleaning(self, input_sequence, expected): """Test cleaning with default to_drop values""" assert clean(input_sequence) == expected @pytest.mark.parametrize("input_sequence,to_drop,expected", [ ([1, 2, 3, 4], (2, 4), [1, 3]), (["a", "b", "c"], ("a", "c"), ["b"]), ([1, "a", 2, "b"], (1, 2), ["a", "b"]), ([1.0, 2.0, 3.0], (2.0,), [1.0, 3.0]), ]) def test_custom_drop_values(self, input_sequence, to_drop, expected): """Test cleaning with custom to_drop values""" assert clean(input_sequence, to_drop) == expected @pytest.mark.parametrize("input_sequence,expected_type", [ ([1, None, 2], list), (tuple([1, None, 2]), tuple), (set([1, None, 2]), set), ]) def test_preserves_input_type(self, input_sequence, expected_type): """Test that the function preserves the input sequence type""" result = clean(input_sequence) assert isinstance(result, expected_type) def test_with_mixed_types(self): """Test cleaning with mixed type values""" input_sequence = [1, None, "hello", [], "", {"key": "value"}, 3.14] expected = [1, "hello", {"key": "value"}, 3.14] assert clean(input_sequence) == expected @pytest.mark.parametrize("invalid_input", [ None, 42, 3.14, ]) def test_invalid_inputs(self, invalid_input): """Test that non-iterable inputs raise TypeError""" with pytest.raises(TypeError): clean(invalid_input) def test_empty_to_drop(self): """Test cleaning with empty to_drop tuple""" input_sequence = [1, None, "", [], 2] assert clean(input_sequence, ()) == input_sequence def test_large_sequence(self): """Test cleaning with a large sequence""" large_list = list(range(1000)) large_list.extend([None] * 1000) result = clean(large_list) assert len(result) == 1000 assert all(isinstance(x, int) for x in result) def test_nested_structures(self): """Test cleaning with nested structures""" input_sequence = [ 1, [2, None], [], (3, ""), {4, None}, ] expected = [1, [2, None], (3, ""), {4, None}] assert clean(input_sequence) == expected def test_with_generator_input(self): """Test cleaning with generator input""" def gen(): yield from [1, None, 2, "", 3] result = clean(gen()) assert isinstance(result, list) assert result == [1, 2, 3] No newline at end of file