Skip to main content
python-utils

⚡ Python Utils

The fast, fully-typed stdlib helpers you keep rewriting — in one tiny, dependency-light package.

PyPI version Python versions CI on master Coverage on master Typed: mypy, pyright and pyrefly Ruff License Monthly downloads

Documentation · PyPI · Source · Issues


Python Utils is a collection of small, battle-tested functions and classes that make everyday Python patterns shorter, safer and faster. No sprawling framework, no heavy dependencies — just the helpers you find yourself re-writing in project after project, packaged once and typed to the hilt.

It has powered production code for years (and is used by libraries such as Django Utils and progressbar2).

✨ Highlights

  • 🪶 Zero-cost imports — thanks to PEP 562 lazy loading, import python_utils pulls in nothing until you actually touch a helper. No asyncio, no typing_extensions, until you ask for them.
  • ⚡ Async-native — acount, abatcher, and timeout/stall detectors bring itertools-style ergonomics to async for.
  • 📦 Smart containers — self-casting dicts, duplicate-proof lists and a sliceable deque.
  • 🔢 Forgiving converters — pull an int/float out of any messy string, scale bytes to KiB/MiB, remap values between ranges (with Decimal precision).
  • ⏱️ Time & retries — human-readable durations plus timeout generators for sampling slow APIs without hanging.
  • 🎯 Fully typed & 100% covered — ships py.typed, passes mypy, basedpyright and pyrefly in strict mode, with 100% test coverage.
  • 🐍 Modern & tiny — Python 3.10+, a single runtime dependency (typing_extensions), BSD-3 licensed.

🗺️ What's inside

Module What you get
converters to_int · to_float · to_str · to_unicode · scale_1024 · remap
formatters camel_to_underscore · apply_recursive · timesince
time format_time · timeout_generator · aio_timeout_generator · aio_generator_timeout_detector
generators batcher · abatcher (batch by size or time interval)
aio acount · acontainer — async itertools
containers CastedDict · LazyCastedDict · UniqueList · SliceableDeque
decorators listify · set_attributes · sample · wraps_classmethod
logger Logged · LoggerBase (+ Logurud via the loguru extra)
import_ import_global — programmatic from x import *
exceptions raise_exception · reraise
terminal get_terminal_size — works in shells, IPython & Jupyter
types handy type aliases (Number, Scope, StringTypes, …)

📦 Installation

pip install python-utils

Optional extras:

pip install 'python-utils[loguru]'   # loguru-backed logging mixin

Python 3.10+ is required. The only runtime dependency is typing_extensions (and it's imported lazily).

🚀 Quickstart

import python_utils

# Pull a number out of any messy string
python_utils.to_int('listening on port=8080', regexp=True)   # 8080

# Human-readable sizes: (value, power-of-1024)
python_utils.scale_1024(1536, 2)                             # (1.5, 1)  -> 1.5 KiB

# Remap a value between ranges (46% volume -> dB on an AVR)
python_utils.remap(46.0, 0.0, 100.0, -80.0, 10.0)           # -38.6

# "time ago" formatting, Django-style
import datetime
python_utils.timesince(datetime.datetime.now() - datetime.timedelta(seconds=61))
# '1 minute and 1 second ago'

Everything is reachable straight off the top-level package (python_utils.<name>) or from its submodule (python_utils.converters.to_int) — pick whichever reads better. Either way, only the modules you touch get imported.

🧰 Examples

🔢 Converters — numbers out of anything
from python_utils import converters

# Extract digits with a built-in or custom regexp
converters.to_int('spam15eggs', regexp=True)          # 15
converters.to_int('nope', default=-1)                 # -1
converters.to_float('pi is 3.14', regexp=True)        # 3.14

# Scale bytes to a sensible unit (value, power) -> 2.0 KiB
converters.scale_1024(2048, 3)                         # (2.0, 1)

# Linear remap; pass a Decimal anywhere to keep full precision
converters.remap(500, 0, 1000, 0, 100)                # 50
import decimal
converters.remap(decimal.Decimal('250.0'), 0.0, 1000.0, 0.0, 100.0)
# Decimal('25.0')
📦 Containers — dicts & lists with super-powers
from python_utils import containers

# Keys and values are cast on the way in
d = containers.CastedDict(int, int)
d['3'] = '4'
d.update({'5': '6'})
d                                   # {3: 4, 5: 6}

# A list that silently drops duplicates (or raises, if you prefer)
u = containers.UniqueList(1, 2, 3)
u.append(2)                         # ignored
u                                   # [1, 2, 3]

# A deque you can actually slice
s = containers.SliceableDeque([1, 2, 3, 4, 5])
s[1:4]                              # SliceableDeque([2, 3, 4])
⚡ Async helpers — itertools for async for
from python_utils import aio, generators

# Async counter (optionally with a delay and a stop value)
async def demo():
    async for i in aio.acount(stop=3):
        print(i)                    # 0, 1, 2

# Batch an async stream by size OR time interval — whichever comes first.
# Great for chunking bursty producers without ever stalling a slow loop.
async def batched():
    async for batch in generators.abatcher(aio.acount(stop=10), batch_size=3):
        print(batch)                # [0, 1, 2], [3, 4, 5], [6, 7, 8], [9]

# Sync batching too:
list(generators.batcher(range(9), 3))   # [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
⏱️ Time & retries — sample slow APIs, format durations
import datetime
from python_utils import time

# Loop over a slow operation, but give up after `timeout` seconds
for i in time.timeout_generator(0.1, interval=0.06):
    ...                             # yields 0, 1, 2 then stops

# Format timedeltas, datetimes and raw seconds uniformly
time.format_time(1)                                         # '0:00:01'
time.format_time(datetime.timedelta(seconds=3661))         # '1:01:01'
time.format_time(datetime.datetime(2000, 1, 2, 3, 4, 5))   # '2000-01-02 03:04:05'
time.format_time(None)                                     # '--:--:--'

There's also aio_timeout_generator (the async for twin) and aio_generator_timeout_detector, which fails fast when an async generator stalls instead of hanging forever.

🔤 Formatters — case conversion & friendly timestamps
from python_utils import formatters

formatters.camel_to_underscore('SpamEggsAndBacon')   # 'spam_eggs_and_bacon'

# Recursively rewrite every key in a nested dict
formatters.apply_recursive(
    formatters.camel_to_underscore,
    {'SpamEggs': {'FooBar': 1}},
)                                                    # {'spam_eggs': {'foo_bar': 1}}
🎀 Decorators — collect generators, tag functions, sample calls
from python_utils import decorators

# Turn a generator into a concrete collection automatically
@decorators.listify()
def numbers():
    yield 1
    yield 2
    yield 3

numbers()                           # [1, 2, 3]

@decorators.listify(collection=dict)
def pairs():
    yield 'a', 1
    yield 'b', 2

pairs()                             # {'a': 1, 'b': 2}

# Attach metadata to a function (handy for the Django admin)
@decorators.set_attributes(short_description='Name')
def upper_case_name(self, obj):
    return f'{obj.first_name} {obj.last_name}'.upper()

# Only actually run ~10% of the calls
@decorators.sample(0.1)
def maybe_log(msg): ...
📝 Logging — a correctly-named logger on every class
from python_utils.logger import Logged

class MyClass(Logged):
    def do_work(self):
        self.info('starting %s', 'work')     # stdlib %-style logging args
        self.error('something went wrong')

MyClass().do_work()

Prefer loguru? Install the extra (pip install 'python-utils[loguru]') and subclass Logurud instead — the same self.info(...) / self.error(...) API, backed by loguru so you keep all its configuration and per-instance context.

🖥️ Terminal & 🧩 misc
from python_utils import terminal, import_
from python_utils.exceptions import raise_exception, reraise

# Robust terminal size (tries IPython/Jupyter, shutil, blessings, ioctl, tput…)
terminal.get_terminal_size()                    # e.g. (80, 24)

# Programmatic `from some_module import *`
import_.import_global('os')

# Build a callable that raises — useful as a default/callback
on_error = raise_exception(ValueError, 'boom')

⚡ Performance: lazy by default

import python_utils is intentionally cheap. Every submodule and every export is wired through a PEP 562 __getattr__, so nothing is imported until first access — and then it's cached. In particular:

  • Need only the synchronous helpers? asyncio is never imported.
  • Even typing_extensions is deferred, so the import graph stays minimal.
import sys
import python_utils                 # imports basically nothing extra

'asyncio' in sys.modules            # False
python_utils.acount                 # now `aio` (and asyncio) load, on demand

See the performance guide for the full story.

📚 Documentation

Full API reference and guides live at https://python-utils.readthedocs.io/en/latest/.

🔒 Security

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

🤝 Contributing

Contributions are very welcome! We keep a strict 100% coverage bar and run ruff, three type checkers and the full test matrix in CI. See CONTRIBUTING.md to get set up.

Support

python-utils is maintained by Rick van Hattem in his own time. It is a small library that four other packages depend on, which means every change is a compatibility question.

If it saved you an afternoon, a tip covers an hour of issue triage: Ko-fi or GitHub Sponsors.

If your company funds its dependencies, this package is on thanks.dev.

ko-fi

📄 License

BSD-3-Clause — see LICENSE.

Metadata

Release files for python-utils 4.1.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 python-utils 4.1.0
File Size Uploaded
python_utils-4.1.0.tar.gz 70.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for python-utils 4.1.0
File Interpreter ABI Platform
python_utils-4.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 120.3 kB

Release files / python_utils-4.1.0.tar.gz

Download URL python_utils-4.1.0.tar.gz
Size 70.9 kB
Tags Source
SHA-256 checksum
How to use checksums
218518ac7ba34a7d8ea66d19274e5246cd5de122efeedcf2e5b6c00965b969b7
BLAKE2b-256 checksum
How to use checksums
8fddfdc08309ce1122e77e5ebc3908aa6f4e2dfed3e08f6987ae650c2591ee0c
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 Oct 2, 2026.

Transparency log

Release files / python_utils-4.1.0-py3-none-any.whl

Download URL python_utils-4.1.0-py3-none-any.whl
Size 49.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dd3c041dae1efc1323209a7598d3a4fe1a832d81be10a98846341af929a0119e
BLAKE2b-256 checksum
How to use checksums
589720994d501834ffe75a8ac065b51e93daf02c85ce347c66ec7e94bc9afab8
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 Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

4.1.0 This release

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.9.1

2 release files

3.9.0

2 release files

3.8.2

2 release files

3.8.1

2 release files

3.8.0

2 release files

3.7.0

2 release files

3.6.1

2 release files

3.6.0

2 release files

3.5.2

2 release files

3.5.1

2 release files

3.5.0

2 release files

3.4.5

2 release files

3.4.4

2 release files

3.4.3

2 release files

3.4.2

2 release files

3.4.1

2 release files

3.4.0

2 release files

3.3.3

2 release files

3.3.2

2 release files

3.3.1

2 release files

3.3.0

2 release files

3.2.3

2 release files

3.2.2

2 release files

3.2.1

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.7.1

2 release files

2.7.0

2 release files

2.6.3

2 release files

2.6.2

2 release files

2.6.1

2 release files

2.6.0

2 release files

2.5.6

2 release files

2.5.5

2 release files

2.5.3

2 release files

2.5.2

2 release files

2.5.1

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.6.2

1 release file

1.6.1

1 release file

1.6.0

1 release file

1.5.2

1 release file

1.5.1

1 release file

1.5.0

1 release file

1.1.2

1 release file

1.1.1

1 release file

1.1.0

1 release file

1.0.1

2 release files

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