Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ pip3 install pythonic-toolbox --upgrade

### decorators

The `decorators` demos highlight reusable wrappers that harden function interfaces, making call sites more forgiving and resilient to transient failures.

#### ignore_unexpected_kwargs

Use `ignore_unexpected_kwargs` to accept forgiving keyword arguments without altering core logic or signatures.

```python3
import pytest
from pythonic_toolbox.decorators.common import ignore_unexpected_kwargs
Expand Down Expand Up @@ -139,6 +143,8 @@ assert Person.greetings(**params)

#### retry

`retry` wraps callables with configurable retry logic so transient errors can be retried transparently.

```python3
import pytest

Expand Down Expand Up @@ -303,8 +309,12 @@ finally:

### deque_utils

`deque_utils` focuses on ergonomic helpers for Python's double-ended queues, emphasizing efficient mutation patterns.

#### deque_pop_any

`deque_pop_any` removes the first matching element from a deque while preserving O(n) traversal semantics.

```python3
from collections import deque

Expand Down Expand Up @@ -339,6 +349,8 @@ assert exec_info.value.args[0] == 'pop from empty deque'

#### deque_split

`deque_split` partitions a deque into multiple deques based on a predicate, keeping operations efficient for queue-like workloads.

```python3
import pytest

Expand Down Expand Up @@ -366,8 +378,12 @@ assert exec_info.value.args[0] == 'num must be integer: 0 <= num <= sys.maxsize'

### dict_utils

The `dict_utils` section collects richer dictionary abstractions and traversal helpers for working with nested mappings.

#### DictObj

`DictObj` exposes dictionary keys as attributes, enabling dot-style access in dynamic data structures.

```python3
from copy import deepcopy

Expand Down Expand Up @@ -592,6 +608,8 @@ assert team.leader == deep_copy_of_team.leader

#### FinalDictObj

`FinalDictObj` freezes dictionaries after construction, safeguarding nested data against accidental mutation.

```python3
from typing import cast

Expand Down Expand Up @@ -663,6 +681,8 @@ assert team.leader == deep_copy_of_team.leader

#### RangeKeyDict

`RangeKeyDict` associates lookup results with numeric ranges, yielding logarithmic-time queries backed by bisect searches.

```python3
import pytest
from pythonic_toolbox.utils.dict_utils import RangeKeyDict
Expand Down Expand Up @@ -787,6 +807,8 @@ assert age_categories_map[Age(70)] == 'Seniors'

#### StrKeyIdDict

`StrKeyIdDict` assigns deterministic integer identifiers to string keys while maintaining bidirectional lookups.

```python3
import pytest
from pythonic_toolbox.utils.dict_utils import StrKeyIdDict
Expand Down Expand Up @@ -878,6 +900,8 @@ assert my_dict['1'] == 'a'

#### collect_leaves

`collect_leaves` traverses nested dictionaries and gathers terminal values into a flat structure.

```python3
from pythonic_toolbox.utils.dict_utils import collect_leaves

Expand Down Expand Up @@ -943,6 +967,8 @@ assert collect_leaves(None) == []

#### dict_until

`dict_until` repeatedly applies mutations to a mapping until a predicate signals completion.

```python3
from pythonic_toolbox.utils.dict_utils import dict_until

Expand All @@ -960,6 +986,8 @@ assert dict_until(data, keys=['pen_name'], terminate=lambda x: x is not None, de

#### select_list_of_dicts

`select_list_of_dicts` filters lists of dictionaries using expressive selection predicates.

```python3
from pythonic_toolbox.utils.dict_utils import select_list_of_dicts

Expand Down Expand Up @@ -1033,6 +1061,8 @@ assert select_list_of_dicts(dict_lst, look_like={'sex': 'male'},

#### unique_list_of_dicts

`unique_list_of_dicts` collapses dictionaries into a unique list based on configurable identity keys.

```python3
from pythonic_toolbox.utils.dict_utils import unique_list_of_dicts

Expand All @@ -1059,6 +1089,8 @@ assert unique_list_of_dicts([]) == []

#### walk_leaves

`walk_leaves` yields a generator over nested key paths and leaf values for introspection-heavy workflows.

```python3
from pythonic_toolbox.utils.dict_utils import walk_leaves

Expand Down Expand Up @@ -1102,8 +1134,12 @@ assert walk_leaves({}, inplace=True) is None

### functional_utils

`functional_utils` gathers lightweight functional-programming inspired helpers that compose common iterable transformations.

#### filter_multi

`filter_multi` composes multiple predicates for iterative filtering, with `lfilter_multi` providing a list materialization helper.

```python3
from pythonic_toolbox.utils.functional_utils import lfilter_multi, filter_multi
from collections.abc import Iterable
Expand Down Expand Up @@ -1145,8 +1181,12 @@ for idx, value in enumerate(filter_multi([is_even, is_divisible_by_5], count(sta

### list_utils

Utilities in `list_utils` provide expressive patterns for curation, ordering, and restructuring of list data.

#### filter_allowable

`filter_allowable` retains items that match allowable values, whether they are literal matches or resolved dynamically.

```python3
from pythonic_toolbox.utils.list_utils import filter_allowable

Expand Down Expand Up @@ -1181,6 +1221,8 @@ assert list(filter_allowable(candidates=[], allow_list=[], block_list=[])) == []

#### sort_with_custom_orders

`sort_with_custom_orders` sorts sequences according to bespoke priority orders or fallback comparators.

```python3
from operator import itemgetter
from typing import List
Expand Down Expand Up @@ -1280,6 +1322,8 @@ assert sort_with_custom_orders(persons, prefix_orders=[Menglong, Person(4, 'Anyo

#### unpack_list

`unpack_list` unpacks nested iterables into positional variables with clear error reporting.

```python3
import pytest
from pythonic_toolbox.utils.list_utils import unpack_list
Expand Down Expand Up @@ -1341,6 +1385,8 @@ with pytest.raises(ValueError):

#### until

`until` iterates through data until a stopping condition is met, mirroring familiar functional-programming patterns.

```python3
from itertools import count

Expand Down Expand Up @@ -1378,8 +1424,12 @@ assert until(numbers, lambda x: x >= 5, default=None, max_iter_num=100) == 5

### string_utils

The `string_utils` module streamlines templating and value substitution when building dynamic strings.

#### substitute_string_template_dict

`substitute_string_template_dict` safely fills placeholders in string templates using dictionary-based parameters.

```python3
from unittest.mock import patch, PropertyMock

Expand Down Expand Up @@ -1456,8 +1506,12 @@ with pytest.raises(CycleError) as exec_info:

### context

`context` demonstrates context managers that gracefully gate execution paths based on runtime conditions.

#### SkipContext

`SkipContext` conditionally suppresses execution within a context manager, ideal for pre-emptive locking or runtime flags.

```python3
import itertools

Expand Down
135 changes: 135 additions & 0 deletions tests/generate_readme_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,135 @@
SHARP = '#'
THREE_BACKTICKS = '```'

MODULE_INTRODUCTIONS: Dict[str, str] = {
'decorators': (
'The `decorators` demos highlight reusable wrappers that harden function '
'interfaces, making call sites more forgiving and resilient to transient '
'failures.'
),
'deque_utils': (
'`deque_utils` focuses on ergonomic helpers for Python\'s double-ended '
'queues, emphasizing efficient mutation patterns.'
),
'dict_utils': (
'The `dict_utils` section collects richer dictionary abstractions and '
'traversal helpers for working with nested mappings.'
),
'functional_utils': (
'`functional_utils` gathers lightweight functional-programming inspired '
'helpers that compose common iterable transformations.'
),
'list_utils': (
'Utilities in `list_utils` provide expressive patterns for curation, '
'ordering, and restructuring of list data.'
),
'string_utils': (
'The `string_utils` module streamlines templating and value substitution '
'when building dynamic strings.'
),
'context': (
'`context` demonstrates context managers that gracefully gate execution '
'paths based on runtime conditions.'
),
}

FUNCTION_INTRODUCTIONS: Dict[str, Dict[str, str]] = {
'decorators': {
'ignore_unexpected_kwargs': (
'Use `ignore_unexpected_kwargs` to accept forgiving keyword arguments '
'without altering core logic or signatures.'
),
'retry': (
'`retry` wraps callables with configurable retry logic so transient '
'errors can be retried transparently.'
),
},
'deque_utils': {
'deque_pop_any': (
'`deque_pop_any` removes the first matching element from a deque while '
'preserving O(n) traversal semantics.'
),
'deque_split': (
'`deque_split` partitions a deque into multiple deques based on a '
'predicate, keeping operations efficient for queue-like workloads.'
),
},
'dict_utils': {
'DictObj': (
'`DictObj` exposes dictionary keys as attributes, enabling dot-style '
'access in dynamic data structures.'
),
'FinalDictObj': (
'`FinalDictObj` freezes dictionaries after construction, safeguarding '
'nested data against accidental mutation.'
),
'RangeKeyDict': (
'`RangeKeyDict` associates lookup results with numeric ranges, yielding '
'logarithmic-time queries backed by bisect searches.'
),
'StrKeyIdDict': (
'`StrKeyIdDict` assigns deterministic integer identifiers to string '
'keys while maintaining bidirectional lookups.'
),
'collect_leaves': (
'`collect_leaves` traverses nested dictionaries and gathers terminal '
'values into a flat structure.'
),
'dict_until': (
'`dict_until` repeatedly applies mutations to a mapping until a '
'predicate signals completion.'
),
'select_list_of_dicts': (
'`select_list_of_dicts` filters lists of dictionaries using expressive '
'selection predicates.'
),
'unique_list_of_dicts': (
'`unique_list_of_dicts` collapses dictionaries into a unique list based '
'on configurable identity keys.'
),
'walk_leaves': (
'`walk_leaves` yields a generator over nested key paths and leaf values '
'for introspection-heavy workflows.'
),
},
'functional_utils': {
'filter_multi': (
'`filter_multi` composes multiple predicates for iterative filtering, '
'with `lfilter_multi` providing a list materialization helper.'
),
},
'list_utils': {
'filter_allowable': (
'`filter_allowable` retains items that match allowable values, whether '
'they are literal matches or resolved dynamically.'
),
'sort_with_custom_orders': (
'`sort_with_custom_orders` sorts sequences according to bespoke '
'priority orders or fallback comparators.'
),
'unpack_list': (
'`unpack_list` unpacks nested iterables into positional variables with '
'clear error reporting.'
),
'until': (
'`until` iterates through data until a stopping condition is met, '
'mirroring familiar functional-programming patterns.'
),
},
'string_utils': {
'substitute_string_template_dict': (
'`substitute_string_template_dict` safely fills placeholders in string '
'templates using dictionary-based parameters.'
),
},
'context': {
'SkipContext': (
'`SkipContext` conditionally suppresses execution within a context '
'manager, ideal for pre-emptive locking or runtime flags.'
),
},
}

URL_PREFIX = "https://github.com/albertmenglongli/pythonic-toolbox/actions/workflows"
BADGE_SUFFIX = "badge.svg?branch=master"

Expand Down Expand Up @@ -148,6 +277,9 @@ def main():
pkg_name = testing_file_path.stem
pkg_name_without_test_ = remove_prefix(pkg_name, 'test_')
contents.append(SHARP * title_level + SPACE + pkg_name_without_test_)
module_intro = MODULE_INTRODUCTIONS.get(pkg_name_without_test_)
if module_intro:
contents.append(module_intro)
pkg = __import__(pkg_name)
name_func_pairs = get_functions_in_pkg(pkg)
block_of_contents_map: DefaultDict[str, List[str]] = extract_block_of_contents(testing_file_path)
Expand All @@ -157,6 +289,9 @@ def main():
title_level += 1
func_name_without_test_ = remove_prefix(func_name, 'test_')
contents.append(SHARP * title_level + SPACE + func_name_without_test_)
func_intro = FUNCTION_INTRODUCTIONS.get(pkg_name_without_test_, {}).get(func_name_without_test_)
if func_intro:
contents.append(func_intro)

source_code_str = getsource(func)
source_codes_lines = deque(source_code_str.split('\n'))
Expand Down
Loading