myBasis: Ergonomic Python Utilities
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 |
IterUtils — partition · 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_all … any_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 |
TextUtils — replace · 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 |
SyntaxUtils — fill_tree · tree_size · pyd_schemify · instance_fields · instance_aliases · nested_replace · import_module · clear_cached_properties SemanticUtils — decimal_to_roman / roman_to_decimal · format_amount · to_singular · to_ordinal · validate_identifier |
System & shell my.utils, my.types |
SystemUtils — posix · 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 |
RegexStore — define · 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 bareimport mystays 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
utfacade flattens all six utility classes into one namespace, sout.partition(...)works without remembering thatpartitionlives onIterUtils. - The type vocabulary comes in pairs:
Atomis a union alias for annotations,Atomsis the matching tuple for runtime checks —isinstance('s', Atoms)isTrue,isinstance([], Atoms)isFalse.
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)
| File | Size | Uploaded | |
|---|---|---|---|
| my_basis-1.0.0.tar.gz | 305.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|