Commit ec1352a5 authored by MacFarland, Midgie's avatar MacFarland, Midgie
Browse files

leftoff here

parent e69bd464
Loading
Loading
Loading
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)
+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}")
+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