Skip to main content

groupie is a module for useful ExceptionGroups

Exceptions in Python can be a very neat way of controling program flow. ExceptionGroups (with an assist from groupie) simplify more complex control flow.

It's features include:

  • Collect exceptions using a context, like contextlib.suppress - but collect them up
  • accumulate exceptions into one big ExceptionGroup to be raised at once
  • downgrade exceptions to warnings presented via logging
  • Static analysis (eg. ruff) compatability notices when you're supressing errors

Think of using this when:

  • Parsing configuration files / objects
  • Parsing code
  • Building a bunch of targets

Useful Patterns

Surface as many user-facing issues as possible at once

It's super annoying as a user to get drip-fed one issue at a time, especially when there's some intensive process leading up to the failure (imagine if pytest stopped at the first failed test each time it was run).

Raise these exceptions for any branch of logic that will individually stop execution, but accumulating these before raising them means that all possible branches are explored as deeply as possible before surfacing the issue.

eg.

time_consuming_setup()

with accumulate(ValueError) as accumulator:
    with accumulator.collector:
        do_one()  # <-- say this fails, we'd usually never know if two and three were going to work!
    with accumulator.collector:
        do_two()
    with accumulator.collector:
        do_three()  # <-- this fails as well!

This will yield an ExceptionGroup showing both do_one and do_three failed at the same time!

Accumulating exceptions in a loop

Because it's so common, there are special methods to condense the boilerplate of accumulating exceptions in a loop.

for collector, item in accumulate(range(10), ValueError):
    with collector:
        do_something(item)

Then the exception group will contain all the exceptions raised in the loop.

downgradeing Exceptions

Say you've deprecated something, but you want to surface it as a warning, later to be promoted to an error:

def do_something():
    with downgrade(DeprecationException):
        raise DeprecationException("This is deprecated - to the other thing.")

    # but, for now, do it anyway...

Attempting to do something a few ways, failing only if none succeed

A common pattern for a user-facing application is to attempt to do something - perhaps load a configuration file - a few different ways, and only fail if none succeed.

Say you allow the configuration file for a tool to exist in a few places.

def load_config():
    possible_locations = [
        "~/.config/my-tool/config.json",
        "./config.json",
        "/etc/my-tool/config.json",
    ]
    collector = Collector(FileNotFoundError)
    for location in possible_locations:
        with collector:
            return load_config_from_file(location)

    # If we haven't already returned, raise an exception describing
    # all the failures we encountered while trying.
    collector.raise_all()

NOTE: the difference accumulate and Collector:

  • accumulate is a context manager that accumulates the exceptions into a single one, and then raises it when exiting the context. It's useful to ensure you definately raise the exception and makes it really hard to miss a check on whether there were any.
  • The Collector is a context manager that collects the exceptions, but it's up to you to handle them (possibly raise them) when you're done with it.

Custom UserException tree

One very useful pattern is to create a tree of Exception types under the standard included types and use them to represent user-facing issues.

class UserException(Exception):
    """Represents a user-facing exception"""

class UserResourceException(UserException):
    """You have a file missing!"""

# etc...

This is particulaly useful because you can pass the UserException type to all the accumulate calls around your code.

You can also add pretty printing to these exceptions (check out rich!) with something like:

class UserException(Exception):
    """Represents a user-facing exception"""

    def __str__(self):
        return f"{self.__class__.__name__}: {self.__doc__}"

Attribution

These utils and patternsstarted life as part of atopile, a compiler to design PCBAs with code

Metadata

Release files for groupie 0.3.1

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

Source distribution (sdist)

Source distribution for groupie 0.3.1
File Size Uploaded
groupie-0.3.1.tar.gz 18.2 kB Details

Built distribution (wheel)

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

Total release size: 25.2 kB

Release files / groupie-0.3.1.tar.gz

Download URL groupie-0.3.1.tar.gz
Size 18.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7fcb0cc303dbbd6ecf8a0ee099a63875992475eb3fcc7083ced136b966e7cccc
BLAKE2b-256 checksum
How to use checksums
8d75a4f5b36d5459178ede3d0168fa3d5c3230059f81309a7d0752ee647f1776
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.

Transparency log

Release files / groupie-0.3.1-py3-none-any.whl

Download URL groupie-0.3.1-py3-none-any.whl
Size 7.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d6a96b67ef3eae5e1a3342fa047a494f5f6f35d89aa1fe9b6c30039b2f132699
BLAKE2b-256 checksum
How to use checksums
c86aad50ba39cee908102529e1cc4746a2d167f03b6b483e92a330e862f2b801
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

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