Skip to main content

bunches

Version PyPI Latest Release GitHub Latest Release
Status Build Status Code Coverage Development Status Project Stability
Documentation Hosted By
Tools Documentation Linter Type Checker Dependency Manager Pre-commit CI Editor Settings Repository Template Dependency Maintainer
Compatibility Compatible Python Versions Linux MacOS Windows
Stats PyPI Download Rate (per month) GitHub Stars GitHub Contributors GitHub Issues GitHub Forks

What is bunches?

bunches provides drop-in replacements for Python's built-in dict and list that add a consistent set of extra tools. Every class in the package:

  • stores its data in a plain Python collection in the contents attribute, so you can always drop back to the built-in type;
  • has an add method, the default way to put data in the collection;
  • has a delete method, the default way to remove data (it is also what del collection[item] calls);
  • has a subset method that returns a new collection containing only some of the data;
  • supports + (returns a new collection with an item added) and += (adds the item in place), both of which use add.

The package is fully typed, has no dependencies, and supports Python 3.11 and later.

Why use bunches?

The built-in collections are excellent, but they leave you to re-implement the same conveniences whenever you build a registry, a set of options, or an ordered group of named items. bunches supplies those conveniences once: wildcard lookups, keys inferred from the items you store, layered dictionaries, and lists that can be searched by name.

Classes

Mappings

  • Dictionary: drop-in replacement for a python dict with an add method for a default mechanism of adding data, a delete method for a default mechanism of deleting data, and a subset method for returning a subset of the key/value pairs in a new Dictionary. Its keys, values and items methods return tuples and it has defaultdict-like behavior when using get.
  • Catalog: wildcard-accepting dict which is intended for storing different options and strategies. It recognizes the special keys all, default and none and returns lists of matches if a list of keys is provided.
  • ChainDict: a Dictionary that stores several dictionaries and searches them in order, like collections.ChainMap.
  • Repository: a dictionary that automatically supplies key names for stored items. The overwrite argument determines if a unique key should always be created or whether entries may be overwritten.

Sequences

  • Listing: drop-in replacement for a python list with an add method for a default mechanism of adding data, a delete method for a default mechanism of deleting data, a prepend method, and a subset method for returning a subset of the items in a new Listing.
  • DictList: iterable with both dict and list interfaces. Stored items must be hashable or have a name attribute.

Getting started

Requirements

bunches requires Python 3.11 or later and runs on Linux, macOS and Windows. It has no third-party dependencies.

Installation

To install bunches, use pip:

pip install bunches

Usage

Dictionary

A Dictionary works like a dict. Its add method updates the stored data, subset returns a new Dictionary, and get falls back on default_factory (a value or a callable) when a key is missing.

from bunches import Dictionary

colors = Dictionary(contents = {'red': 1, 'green': 2}, default_factory = 0)
colors.add({'blue': 3})
colors['blue']  # 3
colors.get('purple')  # 0
colors.subset(include = ['red', 'blue']).contents  # {'red': 1, 'blue': 3}
colors.subset(exclude = 'red').keys()  # ('green', 'blue')
del colors['green']
colors.contents  # {'red': 1, 'blue': 3}
(colors + {'pink': 4}).contents  # {'red': 1, 'blue': 3, 'pink': 4}
colors.contents  # {'red': 1, 'blue': 3}

Catalog

A Catalog is meant for storing options. The all, default and none keys are special and a list of keys returns a list of values.

from bunches import Catalog

models = Catalog(contents = {'tree': 'DecisionTree', 'forest': 'RandomForest'})
models['tree']  # 'DecisionTree'
models[['tree', 'forest']]  # ['DecisionTree', 'RandomForest']
models['all']  # ['DecisionTree', 'RandomForest']
models.default = 'forest'
models['default']  # 'RandomForest'
models['none'] is None  # True

ChainDict

A ChainDict searches its stored dictionaries in order and returns the first match. Set return_first to False to get every match.

from bunches import ChainDict, Dictionary

chain = ChainDict(contents = [Dictionary({'a': 1}), Dictionary({'a': 2, 'b': 3})])
chain['a'], chain['b']  # (1, 3)
chain.return_first = False
chain['a']  # [1, 2]
chain.new_child(Dictionary({'c': 4}))
chain.keys()  # ('c', 'a', 'a', 'b')

Repository

A Repository infers the key for each item added: the item itself if it is a str, its name attribute, or the snake-cased name of its class. Unless overwrite is True, a counter is appended to avoid overwriting existing items.

from dataclasses import dataclass
from bunches import Repository


@dataclass
class Worker:
    name: str


class DataLoader:
    pass


repository = Repository()
repository.add(Worker('ann'))
repository.add(DataLoader())
repository.add(DataLoader())
repository.add(Worker('bob'), key = 'boss')
repository.keys()  # ('ann', 'data_loader', 'data_loader2', 'boss')

Listing

A Listing works like a list. add extends the list with a sequence and appends anything else, and prepend does the same at the beginning.

from bunches import Listing

letters = Listing(contents = ['b', 'c'])
letters.add('d')
letters.add(['e', 'f'])
letters.prepend('a')
letters.contents  # ['a', 'b', 'c', 'd', 'e', 'f']
letters.subset(include = ['a', 'b', 'c'], exclude = 'b').contents  # ['a', 'c']
letters.delete(0)
letters[0]  # 'b'

DictList

A DictList is a list you can also search by name. Items are found by their name attribute or, if they have none, by their own value. Duplicate names are allowed; a DictList of the matches is returned.

from bunches import DictList

workers = DictList(contents = [Worker('ann'), Worker('bob'), Worker('ann')])
workers.keys()  # ('ann', 'bob', 'ann')
workers['bob']  # Worker(name='bob')
len(workers['ann'])  # 2
workers[1]  # Worker(name='bob')
workers.delete('ann')
workers.keys()  # ('bob',)

Choosing what subset returns

Every subset method has a returns argument. 'class' (the default) returns a new instance of the same class, 'copy' returns a deep copy of the instance (keeping its other settings) and 'simple' returns the built-in type. To change the default for the whole package, use bunches.settings.set_subset_return.

colors = Dictionary({'red': 1, 'green': 2})
colors.subset(include = 'red', returns = 'simple')  # {'red': 1}

Naming items

Repository and DictList name items with a global function that you can replace using bunches.settings.set_key_namer.

from bunches import settings

settings.set_key_namer(lambda item: str(item).upper())
Repository({'a': 1}).add('b')
DictList(['x', 'y']).keys()  # ('X', 'Y')
settings.set_key_namer(settings.utilities._namify)

Contributing

Contributors are always welcome. Feel free to grab an issue to work on or make a suggested improvement. If you wish to contribute, please read the Contribution Guide and Code of Conduct.

Similar Projects

  • collections in the Python standard library provides UserDict, UserList, ChainMap and defaultdict, which inspired several of the classes in bunches.
  • sortedcontainers provides sorted drop-in replacements for list, dict and set.

Acknowledgments

I'd also like to extend a special thanks to pawamoy whose excellent mkdocs extensions and utilities are incorporated into snickerdoodle. Some of the scripts, documentation, configuration files, and other CI code were adapted from pawamoy's repositories.

I would also like to thank the University of Kansas School of Law for tolerating and supporting this law professor's coding efforts, an endeavor which is well outside the typical scholarly activities in the discipline.

License

Use of this repository is authorized under the Apache Software License 2.0.

Metadata

Release files for bunches 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bunches 0.3.0
File Size Uploaded Provenance Actions
bunches-0.3.0.tar.gz 132.0 kB Without provenance Details for bunches-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bunches 0.3.0
File Interpreter ABI Platform Actions
bunches-0.3.0-py3-none-any.whl Python 3 none any Details for bunches-0.3.0-py3-none-any.whl

Total release size: 154.7 kB

Release files / bunches-0.3.0.tar.gz

Download URL bunches-0.3.0.tar.gz
Size 132.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2f2ebcc240d3bdb1688d4a3e52e12fe035811adb7c7747124a750e2de27f15d3
BLAKE2b-256 checksum
How to use checksums
80073f8a796b662edd6b5f7810a78d250b9d43524ae64f09223558efcf59941b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / bunches-0.3.0-py3-none-any.whl

Download URL bunches-0.3.0-py3-none-any.whl
Size 22.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d051f7ff6c8c39cd1b002cd1fa78dc349465699b700f09ac3a956828363d6c45
BLAKE2b-256 checksum
How to use checksums
8184b52e0ec8c684ec95d02dce3a9a1f080e688abbd41846b8475d49258d0e21
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for bunches 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page