pax_global_header00006660000000000000000000000064140154477720014525gustar00rootroot0000000000000052 comment=e22d742d99cc2852bd2006087333cde096749c58 pudo-banal-fdbb8de/000077500000000000000000000000001401544777200144075ustar00rootroot00000000000000pudo-banal-fdbb8de/.bumpversion.cfg000066400000000000000000000002741401544777200175220ustar00rootroot00000000000000[bumpversion] current_version = 1.0.6 tag_name = {new_version} commit = True tag = True [bumpversion:file:setup.py] search = version="{current_version}" replace = version="{new_version}" pudo-banal-fdbb8de/.github/000077500000000000000000000000001401544777200157475ustar00rootroot00000000000000pudo-banal-fdbb8de/.github/workflows/000077500000000000000000000000001401544777200200045ustar00rootroot00000000000000pudo-banal-fdbb8de/.github/workflows/build.yml000066400000000000000000000014571401544777200216350ustar00rootroot00000000000000name: build on: [push] jobs: python: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Show ref run: | echo "$GITHUB_REF" - name: Set up Python uses: actions/setup-python@v1 with: python-version: '3.x' - name: Install dependencies env: DEBIAN_FRONTEND: noninteractive run: | pip install -e '.[dev]' - name: Validate typing run: | mypy --strict . - name: Build a distribution run: | python setup.py sdist bdist_wheel - name: Publish a Python distribution to PyPI if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.pypi_password }} pudo-banal-fdbb8de/.gitignore000066400000000000000000000022151401544777200163770ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class .vscode # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ pudo-banal-fdbb8de/LICENSE000066400000000000000000000020651401544777200154170ustar00rootroot00000000000000MIT License Copyright (c) 2017 Friedrich Lindenberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pudo-banal-fdbb8de/MANIFEST.in000066400000000000000000000001041401544777200161400ustar00rootroot00000000000000include README.md LICENSE include MANIFEST.in include banal/py.typedpudo-banal-fdbb8de/Makefile000066400000000000000000000004451401544777200160520ustar00rootroot00000000000000 all: dists dists: clean python setup.py sdist bdist_wheel release: dists twine upload dist/* clean: rm -rf dist build .eggs find . -name '*.egg-info' -exec rm -fr {} + find . -name '*.egg' -exec rm -f {} + find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + pudo-banal-fdbb8de/README.md000066400000000000000000000012701401544777200156660ustar00rootroot00000000000000# banal Commons of Python micro-functions. This basically an out-sourced, shared utils module with a focus on functions that buffer type uncertainties in Python (e.g. "is this a list?"). Rules: * Functions are properly typed, library passes `mypy`. * Cannot depend on anything but six and the standard library ## Functions * ``is_listish``: check if something is list-ish * ``is_mapping``: check if an object is dict-ish * ``ensure_list``: make sure an argument is a list, or make it into a single-element list * ``clean_dict``: remove null values from a dict, recursively * ``decode_path``: decode a path name to be unicode * ``hash_data``: generate a SHA1 from a dict of reasonable objectspudo-banal-fdbb8de/banal/000077500000000000000000000000001401544777200154645ustar00rootroot00000000000000pudo-banal-fdbb8de/banal/__init__.py000066400000000000000000000011741401544777200176000ustar00rootroot00000000000000from banal.lists import is_sequence, is_listish from banal.lists import ensure_list, unique_list from banal.lists import first, chunked_iter, chunked_iter_sets from banal.dicts import is_mapping, clean_dict from banal.dicts import ensure_dict, keys_values from banal.filesystem import decode_path from banal.cache import hash_data from banal.bools import as_bool __all__ = [ "is_sequence", "is_listish", "ensure_list", "unique_list", "chunked_iter", "chunked_iter_sets", "first", "as_bool", "is_mapping", "clean_dict", "ensure_dict", "keys_values", "decode_path", "hash_data", ] pudo-banal-fdbb8de/banal/bools.py000066400000000000000000000005511401544777200171550ustar00rootroot00000000000000from typing import Any BOOL_TRUEISH = ["1", "yes", "y", "t", "true", "on", "enabled"] def as_bool(value: Any, default: bool = False) -> bool: if isinstance(value, bool): return value if value is None: return default value = str(value).strip().lower() if not len(value): return default return value in BOOL_TRUEISH pudo-banal-fdbb8de/banal/cache.py000066400000000000000000000032461401544777200171060ustar00rootroot00000000000000import types from hashlib import sha1 from itertools import chain from typing import Any, Union, Iterable from datetime import date, datetime from banal.dicts import is_mapping from banal.lists import is_sequence def _bytes_str(obj: Union[str, bytes]) -> bytes: if not isinstance(obj, str): return obj return obj.encode("utf-8") def bytes_iter(obj: Any) -> Iterable[bytes]: """Turn a complex object into an iterator of byte strings. The resulting iterator can be used for caching. """ if obj is None: return elif isinstance(obj, (bytes, str)): yield _bytes_str(obj) elif isinstance(obj, (date, datetime)): yield _bytes_str(obj.isoformat()) elif is_mapping(obj): if None in obj: yield from bytes_iter(obj.pop(None)) for key in sorted(obj.keys()): for out in chain(bytes_iter(key), bytes_iter(obj[key])): yield out elif is_sequence(obj): if isinstance(obj, (list, set)): try: obj = sorted(obj) except Exception: pass for item in obj: for out in bytes_iter(item): yield out elif isinstance( obj, ( types.FunctionType, types.BuiltinFunctionType, types.MethodType, types.BuiltinMethodType, ), ): yield _bytes_str(getattr(obj, "func_name", "")) else: yield _bytes_str(str(obj)) def hash_data(obj: Any) -> str: """Generate a SHA1 from a complex object.""" collect = sha1() for data in bytes_iter(obj): collect.update(data) return collect.hexdigest() pudo-banal-fdbb8de/banal/dicts.py000066400000000000000000000020551401544777200171460ustar00rootroot00000000000000from typing import Any, Dict, List, TypeVar from collections.abc import Mapping from banal.lists import is_sequence, ensure_list K = TypeVar("K") V = TypeVar("V") def is_mapping(obj: Any) -> bool: return isinstance(obj, Mapping) def ensure_dict(obj: Any) -> Dict[K, V]: if is_mapping(obj) or hasattr(obj, "items"): return dict(obj.items()) return {} def clean_dict(data: Any) -> Any: """Remove None-valued keys from a dictionary, recursively.""" if isinstance(data, Mapping): out = {} for k, v in data.items(): if v is not None: out[k] = clean_dict(v) return out elif is_sequence(data): return [clean_dict(d) for d in data if d is not None] return data def keys_values(data: Dict[K, V], *keys: K) -> List[V]: """Get an entry as a list from a dict. Provide a fallback key.""" values: List[V] = [] if isinstance(data, Mapping): for key in keys: if key in data: values.extend(ensure_list(data[key])) return values pudo-banal-fdbb8de/banal/filesystem.py000066400000000000000000000004701401544777200202230ustar00rootroot00000000000000import sys from typing import Optional def decode_path(file_path: Optional[str]) -> Optional[str]: """Turn a path name into unicode.""" if file_path is None: return None if isinstance(file_path, bytes): file_path = file_path.decode(sys.getfilesystemencoding()) return file_path pudo-banal-fdbb8de/banal/lists.py000066400000000000000000000040651401544777200172010ustar00rootroot00000000000000from typing import cast, overload, Optional, List, Set, Any, TypeVar from typing import Generator, Sequence, Iterable T = TypeVar("T") def is_sequence(obj: Any) -> bool: return isinstance(obj, Sequence) and not isinstance(obj, (str, bytes)) def is_listish(obj: Any) -> bool: """Check if something quacks like a list.""" if isinstance(obj, (list, tuple, set)): return True return is_sequence(obj) def unique_list(lst: Sequence[T]) -> List[T]: """Make a list unique, retaining order of initial appearance.""" uniq = [] for item in lst: if item not in uniq: uniq.append(item) return uniq @overload def ensure_list(obj: None) -> List[None]: pass @overload def ensure_list(obj: Iterable[T]) -> List[T]: pass @overload def ensure_list(obj: T) -> List[T]: pass def ensure_list(obj: Any) -> List[T]: """Make the returned object a list, otherwise wrap as single item.""" if obj is None: return [] if is_listish(obj): return [o for o in cast(Sequence[T], obj)] return [cast(T, obj)] def chunked_iter( iterable: Iterable[T], batch_size: int = 500 ) -> Generator[List[T], None, None]: """Pick `batch_size` items from an iterable and treat them as a batch list.""" batch = list() for item in iterable: batch.append(item) if len(batch) >= batch_size: yield batch batch = list() if len(batch) > 0: yield batch def chunked_iter_sets( iterable: Iterable[T], batch_size: int = 500 ) -> Generator[Set[T], None, None]: """Pick `batch_size` items from an iterable and treat them as a batch set.""" batch = set() for item in iterable: batch.add(item) if len(batch) >= batch_size: yield batch batch = set() if len(batch) > 0: yield batch def first(lst: Sequence[T]) -> Optional[T]: """Return the first non-null element in the list, or None.""" item: T for item in ensure_list(lst): if item is not None: return item return None pudo-banal-fdbb8de/banal/py.typed000066400000000000000000000000001401544777200171510ustar00rootroot00000000000000pudo-banal-fdbb8de/setup.cfg000066400000000000000000000000751401544777200162320ustar00rootroot00000000000000[bdist_wheel] universal=1 [metadata] license_file = LICENSE pudo-banal-fdbb8de/setup.py000066400000000000000000000021311401544777200161160ustar00rootroot00000000000000from setuptools import setup # type: ignore with open("README.md") as f: long_description = f.read() setup( name="banal", version="1.0.6", description="Commons of banal micro-functions for Python.", long_description=long_description, long_description_content_type="text/markdown", classifiers=[ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], keywords="utilities commons functions", author="Friedrich Lindenberg", author_email="friedrich@pudo.org", url="http://github.com/pudo/banal", license="MIT", namespace_packages=[], package_data={"banal": ["py.typed"]}, packages=["banal"], include_package_data=True, zip_safe=False, install_requires=[], extras_require={ "dev": [ "mypy", "wheel", ] }, )