Skip to main content

logo

myBasis: Ergonomic Python Utilities

Pipeline Status Test Coverage License

PyPI Version Python Version PyPI Wheel PyPI Types

pre-commit pyrefly ruff

The myBasis utility package — imported as my — is a broad extension of the Python standard library centered on text processing, functional programming, and runtime type coercion. The use cases are diverse enough to not enumerate them all here, but they all share a strong sense of discipline: all code is thoroughly typed, tested, and documented following my best pass at best practices (it does get easier!).

I made this module to streamline some patterns that seemed both A) frequently-relevant and feasible to streamline. The repo thus grew alongside my projects over time, a genesis which gives it the advantage of being in daily use by the original author in multiple examples right off the bat.

Its breadth is somewhat unusual: any given application will probably use a small subset of the contents, so it shines where dependency purity isn't paramount — personal projects, local dev scripts, offline data processing, prototypes, and the project you're working on right now are the ideal usecases.1. As a rough sense of scale: a bare pip install my-basis pulls a couple dozen distributions (on the order of ~80 MB unpacked), and turning on every optional extra can push a full environment past ~290 MB because of heavy common dependencies like pandas, numpy, and various pieces of rubble amongst the ruins Google's Python SDK ecosystem. I do still kinda recommend that for personal scripts/admin/env/dev projects, where having the ability to easily work with google sheets, cast types, or performantly write all kinds of filetypes is worth more than some disk space.

Exports

The package is built into a mostly-flat tree with 7 branches, but the exception ends up making the rule: the first branch is utils, the catch-all for dependency-free, relatively-stateless utility functions covering six distinct arenas.

Area Exports
Iteration
my.utils
IterUtilspartition · multi_partition · type_partition · bucket · find · find_key · next_in · condense · map_condense · get_all · get_any · get_first · val_map · attr_map · inverse_map · apply · safe · normalize · indexof · has_all / has_any / has_only / has_none · all_has_allany_has_any · predicate · normalize_predicate · shared_prefix · shared_suffix · common_elements · exclusive_elements · drop_at · drop_duplicates · repeat_until_complete · build · map_items
Text
my.utils, my.types
TextUtilsreplace · split_into · regex_dict · regex_array · multi_rgx · strip_quotes · clean_string · wrap · to_words · line_num · parse_domain · indent / unindent · wrap_paragraphs / unwrap_paragraphs
Buffer — a mutable text container for iterative string surgery
Syntax & semantics
my.utils
SyntaxUtilsfill_tree · tree_size · pyd_schemify · instance_fields · instance_aliases · nested_replace · import_module · clear_cached_properties
SemanticUtilsdecimal_to_roman / roman_to_decimal · format_amount · to_singular · to_ordinal · validate_identifier
System & shell
my.utils, my.types
SystemUtilsposix · posix_since · validate_dir / validate_file · path · path_sub · is_pathy · from_file / to_file (+ json / yaml / toml / pickle variants) · serialize · log / info / warn / error · multiprint · debug_fence · is_installed · mock_if_uninstalled · get_terminal_width · terminal_linewrap · zsh_colorize · print_in_color · confirm / auto_confirm
Command — durable shell invocations, sync or async
Platform — a small enum of supported OSes
Observability
my.utils
MetricUtils ([metrics] extra) — setup_logging · setup_fire_logging · setup_metrics · setup_warnings · measure_context · monitor
Vibe typing
my.typing
ty / Typist — the cast / check / match singleton
MyType — any annotation, parsed into one introspectable node
AutocastModel — a Pydantic base that casts on validation
TypeCast/tyt · TypeCheck/tyc · TypeMatch/tym · CastFlags · TypeArg
Type vocabulary
my.infra
Union aliases with matching isinstance tuples: Atom/Atoms · Scalar/Scalars · Real/Reals · String/Strings · Time/Times · Vec/Vecs · Map/Maps · Struct/Structs · Func/Funcs · Stream/Streams · Model — plus the generic alias quartet FuncT / MapT / VecT / StructT
Reusable types
my.types
MyEnum — enums with forgiving parsing & arithmetic
Span — immutable half-open [start, end) intervals
UniqueId / Uid — validated uuid4 wrappers
Predicate — string-set predicates for vibe-typed matching
Regex
my.regex
RegexStoredefine · compose · search / findall / finditer / fullmatch · fullsplit · polymatch · route_match · apply · filter · atom · pretty_print
MatchData — ergonomic match results, repeated groups included
RegexDebugger — find out why a pattern fails
COMMON_RGXS — a battery of ready-made patterns (url, md_url, tld, …)
meta layer: Regex · Tree · RgxAtom · GroupAtom · SetAtom · GroupKind · Quantifier · ParseData · META_RGXS
Caches
my.caches
Cache — pruned LRU dict
NestedCache — hierarchical, self-pruning levels
FileCache — two-level memory + disk
PickleCache — pickle-backed persistence with TTL
Interfaces
my.apis
env / Environment — typed, ergonomic environment variables
fs / Filesystem / PATHS — a named registry of filesystem paths
GoogleSheet ([google] extra) — sheets in, DataFrames out
File formats
my.files
Markdown — fence-aware hierarchical document trees: parse · walk · edit · render

A few conventions worth knowing up front:

  • Everything is one import away. from my import ut, ty, Span, Markdown — the root re-exports the whole public surface, while the heavier leaves (apis, files) load lazily so a bare import my stays fast.
  • Every utility class has a snake_case twin. iter_utils is IterUtils, text_utils is TextUtils, and so on — pick the import style you like; they are literally the same object.
  • The ut facade flattens all six utility classes into one namespace, so ut.partition(...) works without remembering that partition lives on IterUtils.
  • The type vocabulary comes in pairs: Atom is a union alias for annotations, Atoms is the matching tuple for runtime checks — isinstance('s', Atoms) is True, isinstance([], Atoms) is False.

Install

my-basis is on PyPI, which makes it instantly available instantly via pip install my-basis, uv add my-basis, etc.

The 1.0 release has been cut, beyond which I intend to enforce strict semver; don't expect breaking changes any time in the foreseeable future. If you're like me (i.e. have ADHD?), this library will spark the most joy when you are vaguely aware of its contents and know it's at your fingertips at any time while coding; not only can it save you a bunch of time writing functions, but the prospect of attempting functions with it in hand is so much more approachable that a lot more ends up getting done.

If that makes sense? I guess I'm really trying to sell you the promise of bolder, quicker software engineering.

# pyproject.toml
dependencies = [
  "my-basis",
  # ...
]

[tool.uv.sources]
my-basis = { git = "https://gitlab.com/doering-ai/libs/basis.git", tag = "stable" }

Run uv sync to install.

Refactor an existing repository

The 1.0 line also packages a read-only intake tool and the adopt-my-basis agent skill. From a Python repository you want to assess:

uvx --from my-basis my-basis-adopt skill path
uvx --from my-basis my-basis-adopt skill export .agents/skills/adopt-my-basis
uvx --from my-basis my-basis-adopt prepare .

Give the printed intake.json path to your agent and ask it to use adopt-my-basis. The scanner inventories files, dependency declarations, Python compatibility, candidate native gates, and regex structure without importing the target package or editing its source. The agent then validates an evidence-bound proposal and can render a high-level MyST, standalone HTML, or Typst/PDF report with exact merge and revision instructions. A justified decline or no-op is a successful result; the skill is designed to preserve dependency budgets and deliberate failure paths, not maximize adoption.

The one-shot commands above require no persistent installation. skill path exposes the packaged source directly; skill export is only needed when the receiving agent needs its own catalog copy. See my/skills/adopt-my-basis/SKILL.md for the full workflow and the runnable RegexStore adoption guide for complex grammars.

Optional extras

Core stays as small as this library knows how to be; everything heavier hangs off an extra you opt into with pip install my-basis[<extra>] (or uv add my-basis --extra <extra>):

Extra Unlocks
metrics MetricUtils — Logfire/OpenTelemetry logging, metrics counters, and instrumentation helpers.
google GoogleSheet — read/write Google Sheets as pandas DataFrames, with OAuth2 handled for you.
myst MyST markdown syntax (admonitions, directives, ...) in Markdown.render()'s formatting pass.
terminal The pyratatui-backed terminal-art demos under my/scripts/tuitorii/.
aiohttp A convenience pin so MetricUtils.setup_fire_logging() can auto-instrument your app's aiohttp client when one's already installed — gates nothing on its own.

Call a [metrics] or [google] method without installing its extra and you get an actionable ImportError naming the exact extra to add, not a bare traceback.

Quickstart

The core loop: cast untyped data into a target type, check whether a value already fits one, and introspect a type itself as a MyType node.

from my import ty, MyType

# Cast: coerce arbitrary data into a target type, best-effort.
ty.cast('42', int)                              # -> 42
ty.cast(['1', '2', '3'], list[int])             # -> [1, 2, 3]   (every element coerced)
ty.cast({'a': '1', 'b': '2'}, dict[str, int])   # -> {'a': 1, 'b': 2}

# Check: does this value already conform to a type, without coercing it?
ty.check(42, int)      # -> True
ty.check('42', int)    # -> False

# Match: the best-of-both hybrid -- does it fit, or could it be made to?
ty.match('hello', str | int)   # -> True

# MyType: parse any type expression into an introspectable node.
t = MyType(dict[str, int])
t.main    # -> <class 'dict'>
t.args    # -> (MyType[str], MyType[int])
t.root    # -> dict[str, int]

ty is the package-wide Typist singleton — the cast/check/match chambers composed onto one object. The rest of the library follows the same grain: import one name, get one coherent tool.

The grand tour

Seven subpackages, in rough order from most- to least-general. Every snippet below is real, executed output — and every non-trivial method in the library has an example like these in its docs.

1. my.utils — Pure, Typed Functional Utilities

Six static utility classes — IterUtils, TextUtils, SyntaxUtils, SemanticUtils, SystemUtils, MetricUtils — composed into the single flat facade ut, so you call ut.method() without caring which class defines it.

from my import ut

ut.partition([1, 2, 3, 4], lambda x: x % 2 == 0)   # -> ([1, 3], [2, 4])
ut.condense(['a', None, '', 'b', 0])               # -> ['a', 'b']
ut.find([3, 8, 2], lambda x: x > 5)                # -> 1   (the index, not the value)

2. my.typing — Vibe Typists

The crown jewel: runtime type coercion built for the age of language models, where a tool call's arguments are almost the right shape a thousand times a day. Beyond the ty loop above, AutocastModel bakes the cast into Pydantic validation itself:

from my import AutocastModel

class Settings(AutocastModel):
    port: int = 0
    debug: bool = False
    tags: list[str] = []

s = Settings(port='8080', debug='true', tags='solo')
(s.port, s.debug, s.tags)   # -> (8080, True, ['solo'])

3. my.types — Extensible, Ergonomic Miscellaneous Types

Small, sharp classes that extend the built-ins with the affordances they always seemed to be missing — all Pydantic-native, all serializable.

from my import Span

a, b = Span(3, 9), Span(8, 12)
a.delta           # -> 6
a.intersects(b)   # -> True

4. my.regex — Optimized, Readable Regular Expressions

RegexStore treats patterns as a managed vocabulary: define them once, compose them by name, and get MatchData results that make repeated groups pleasant. A stocked COMMON_RGXS store ships in the box, and a whole meta layer can parse, optimize, and explain the patterns themselves.

from my import RegexStore, COMMON_RGXS

COMMON_RGXS.findall('url', 'visit https://example.com or www.foo.dev')
# -> [MatchData("https://example.com" -> {'url': ['example.com']}),
#     MatchData("www.foo.dev" -> {'url': ['foo.dev']})]

store = RegexStore()
store.define('greeting', r'hello (?P<name>\w+)')
print(store.search('greeting', 'hello world'))   # -> name: world

5. my.caches — Extensible, Performant Local Caches

Four Pydantic-validated caches, one per access pattern: Cache (pruned LRU dict), NestedCache (hierarchical levels), FileCache (memory over disk), and PickleCache (persistent, TTL-invalidated).

from my import Cache

cache = Cache(max_size=256)
cache['answer'] = 42
cache['answer']   # -> 42

6. my.apis — API Wrappers

Ready-made singletons for the resources every script ends up touching: env for typed environment variables, fs for a named registry of paths, and GoogleSheet for spreadsheets as DataFrames.

import os
os.environ['DEMO_FLAG'] = 'true'   # (before the first `my.apis` import -- `env` snapshots on load)

from my import env

env.get('DEMO_FLAG')    # -> 'true'
env.flag('DEMO_FLAG')   # -> 1   (0 when unset)

7. my.files — File Formats

Markdown parses a document into a hierarchical, fence-aware node tree — every section a node you can walk, query, edit, and render back out.

from my import Markdown

root = Markdown.parse('# Title\n\nIntro prose\n\n## Section A\n\nBody\n')[0]
[str(node).splitlines()[0] for node in root.walk()]   # -> ['# Title', '## Section A']

Documentation

The full documentation — one page per class, an example for every non-trivial method — is a Sphinx + MyST + furo site that builds in seconds:

task docs   # -> docs/_build/index.html

A hosted copy on ReadTheDocs is provisioned-but-pending; until it lands, the docs links in this README render directly on GitLab as well.

Caveats

Pydantic-first

You can absolutely use this package without using Pydantic yourself, but you'd be missing out on a lot of the ergonomic benefits: basically every class is a Pydantic model, and the logging functionality in MetricUtils exclusively supports Pydantic's Logfire.

Python 3.12+

The project is written in modern Python syntax (requires-python >= 3.12) and the typing subpackage leans hard on recent typing semantics — PEP 695 generics throughout, and typing_extensions as the one compatibility shim, for the handful of constructs that only reached the stdlib in 3.13 (TypeIs, PEP 696 type-parameter defaults). Every release is tested against 3.12, 3.13, and 3.14 (task test:matrix), and the declared dependency floors are exercised too (task test:floor).

3.12 is a deliberate floor rather than a stepping stone: below it, PEP 695 syntax stops parsing, and the 20-odd modules that would need rewriting are the package's core — not optional leaves that could be excluded on old runtimes. If an older runtime blocks you, either let me know — or lift the one or two modules you need straight out of the repo; the subpackages are deliberately self-contained.

Development

GitLab is the canonical development forge. The GitHub repository is a read-only source mirror; report issues and propose changes at https://gitlab.com/doering-ai/libs/basis.

Contributing

The project was built over the course of 2025 for its author's own use, so it's definitely opinionated — influenced by a weathered respect for polymorphism, an addiction to ergonomic code in the Don-Norman sense, and a reliance on symbolic, deterministic devtools (heavy typing, even at runtime). If any of that resonates: get in touch, or open an issue or merge request on GitLab.

Licensed under MPL-2.0.

Release files for my-basis 1.0.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 my-basis 1.0.0
File Size Uploaded
my_basis-1.0.0.tar.gz 305.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for my-basis 1.0.0
File Interpreter ABI Platform
my_basis-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 647.8 kB

Release files / my_basis-1.0.0.tar.gz

Download URL my_basis-1.0.0.tar.gz
Size 305.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e9206ff2f17927b093c7f18724556e330a06512acdc4fe1aa9c169b38c26db5c
BLAKE2b-256 checksum
How to use checksums
52089139f757e32b9c3f269d6aaf0f4ae87c295ae0446f683e73449a0b096fa7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / my_basis-1.0.0-py3-none-any.whl

Download URL my_basis-1.0.0-py3-none-any.whl
Size 342.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1ea979bbba0d6a4204c83c65763af5c8fec43a15a3f210493f67415b62ffed4
BLAKE2b-256 checksum
How to use checksums
c1577a1417ff1aca6c50f49626940b89dac0abb1c59d5f34a34d17cfef94f548
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

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