Making types flexible.
Project description
flextype
flextype makes working with types in Python more flexible.
It provides primitives for writing code with conditional dispatch paths for optional dependencies.
Additionally, it extends the idea of dynamic subclass registrations provided by abc.ABCMeta to the instance-level, enabling the definition of types with dynamically defined instance sets.
This is useful to define marker types, enabling conditional dispatch of objects to different implementations based on those marker types.
Install
flextype requires Python 3.13 or newer.
pip install flextype
uv add flextype
Quickstart
Lazy Type Checking
lazy_isinstance and lazy_issubclass work like isinstance and
issubclass, but type references may be real classes, fully qualified string
names, unions, or tuples. String references are matched against the actual
runtime type name, so optional dependencies do not need to be imported just to
perform a check.
from pathlib import Path
from flextype import lazy_isinstance, lazy_issubclass
path = Path("README.md")
assert lazy_isinstance(path, "pathlib.Path")
assert lazy_isinstance(path, (Path, "os.PathLike"))
assert lazy_issubclass(type(path), ("pathlib.Path", str))
flexdispatch
flexdispatch is a lazy alternative to functools.singledispatch. It accepts
regular registrations, lazy string registrations, and delayed registrations.
from flextype import flexdispatch
@flexdispatch
def render(value: object) -> str:
return "object"
@render.register("pathlib.Path")
def render_path(value: object) -> str:
return f"path: {value}"
String registrations are resolved lazily. In the example above, pathlib.Path
is converted into the real class only after a matching value is dispatched.
Delayed registrations are useful when the implementation itself lives in an optional integration module. Register a callback against a lazy type name, then import the module that defines the regular registrations only when that type is first encountered.
# myapp/render.py
from flextype import flexdispatch
@flexdispatch
def render(value: object) -> str:
return repr(value)
@render.delayed_register("pandas.DataFrame")
def _load_pandas_handlers(seen_type: type) -> None:
import myapp.pandas_render # noqa: F401
# myapp/pandas_render.py
import pandas as pd
from myapp.render import render
@render.register(pd.DataFrame)
def render_dataframe(value: pd.DataFrame) -> str:
return value.to_markdown()
The delayed callback receives the concrete matching type and runs once. After
the import, dispatch uses the regular pd.DataFrame registration.
Registries
ProtocolRegistry is useful for defining marker types: runtime-only semantic
categories that objects can opt into without changing their inheritance
hierarchy. Unlike regular protocols, these markers can distinguish two objects
of the same class. That makes them useful for conditional dispatch based on
state, provenance, validation, or other facts that are not captured by the
object's structural interface.
from flextype import ProtocolRegistry, flexdispatch
class Verified(ProtocolRegistry, structural_checking=False):
pass
@flexdispatch
def publish(document: object) -> str:
return "manual review required"
@publish.register(Verified)
def publish_verified(document: Verified) -> str:
return "publish immediately"
class Document:
def __init__(self, title: str) -> None:
self.title = title
draft = Document("Draft")
release = Document("Release")
Verified.register_instance(release)
assert publish(draft) == "manual review required"
assert publish(release) == "publish immediately"
Use __instancehook__ when membership in a marker type should be dynamic. It is
called during isinstance(value, MarkerType) and can classify objects by runtime
state, contents, or other predicates instead of explicit registration.
from flextype import ProtocolRegistry, flexdispatch
class IntList(ProtocolRegistry, structural_checking=False):
@classmethod
def __instancehook__(cls, instance: object, /) -> bool:
return isinstance(instance, list) and all(
isinstance(item, int) for item in instance
)
@flexdispatch
def summarize(value: object) -> str:
return "not an int list"
@summarize.register(IntList)
def summarize_int_list(value: list[int]) -> str:
return f"sum={sum(value)}"
assert summarize([1, 2, 3]) == "sum=6"
assert summarize([1, "2", 3]) == "not an int list"
Return NotImplemented from __instancehook__ to fall back to regular
inheritance and explicit register / register_instance checks.
By default, ProtocolRegistry preserves normal protocol structural checks. Pass
structural_checking=False when the type should behave as an explicit marker
instead: only inheritance, explicit registrations, and __instancehook__ decide
whether an object is an instance of the marker type.
ProtocolRegistry is backed by ProtocolRegistryMeta, which extends the ABC
registration concept to instance-level registrations. Registry and
RegistryMeta expose the same mechanism for non-protocol marker classes, and
the metaclasses are exported too when direct metaclass use is the better fit.
Development
uv run ruff check
uv run ty check
uv run pytest
Pre-commit hooks are managed with prek:
uv run prek install
uv run prek run --all-files
License
This project is licensed under the Apache License 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flextype-0.1.1.tar.gz.
File metadata
- Download URL: flextype-0.1.1.tar.gz
- Upload date:
- Size: 20.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfe12138039816badf26b57c6944112949884045e4ebfef51c7b7d19bc4a0264
|
|
| MD5 |
a5b71fba59b43048eb93b73de1ac92c5
|
|
| BLAKE2b-256 |
9985f1b45b7d586d9dda4e0e16625c416099591131cbc1dbc17995a6118abb3a
|
Provenance
The following attestation bundles were made for flextype-0.1.1.tar.gz:
Publisher:
publish-release.yml on Cortys/flextype
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flextype-0.1.1.tar.gz -
Subject digest:
dfe12138039816badf26b57c6944112949884045e4ebfef51c7b7d19bc4a0264 - Sigstore transparency entry: 1979356513
- Sigstore integration time:
-
Permalink:
Cortys/flextype@7e67eb89be50e4df3ceb1d7ff1c80c17fef702bc -
Branch / Tag:
refs/tags/0.1.1 - Owner: https://github.com/Cortys
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-release.yml@7e67eb89be50e4df3ceb1d7ff1c80c17fef702bc -
Trigger Event:
release
-
Statement type:
File details
Details for the file flextype-0.1.1-py3-none-any.whl.
File metadata
- Download URL: flextype-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a86f12428bfdfdf5a52c02b6c53c08d5c4f02bce75d8d180182df0c538d03674
|
|
| MD5 |
a0929c87b8714dad4729450b0103190a
|
|
| BLAKE2b-256 |
fb6f8b045677127b6fa5a64b2ebcd1cec8d6f03dbcacdebc4a14be92e5df8fa0
|
Provenance
The following attestation bundles were made for flextype-0.1.1-py3-none-any.whl:
Publisher:
publish-release.yml on Cortys/flextype
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flextype-0.1.1-py3-none-any.whl -
Subject digest:
a86f12428bfdfdf5a52c02b6c53c08d5c4f02bce75d8d180182df0c538d03674 - Sigstore transparency entry: 1979356656
- Sigstore integration time:
-
Permalink:
Cortys/flextype@7e67eb89be50e4df3ceb1d7ff1c80c17fef702bc -
Branch / Tag:
refs/tags/0.1.1 - Owner: https://github.com/Cortys
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-release.yml@7e67eb89be50e4df3ceb1d7ff1c80c17fef702bc -
Trigger Event:
release
-
Statement type: