interface-contract
Strict runtime interface contracts for Python, with definition-time failures and signature-aware structural checks.
pip install interface-contract
from interface_contract import Interface, default
class Repository(Interface):
def find(self, item_id: int) -> str: ...
def save(self, item: str) -> None: ...
@property
def name(self) -> str: ...
@default
def describe(self) -> str:
return f"repository<{self.name}>"
class SqlRepository(Repository):
def find(self, item_id: int) -> str:
return f"row {item_id}"
def save(self, item: str) -> None:
pass
@property
def name(self) -> str:
return "sql"
If SqlRepository misses a required member, changes its descriptor kind, or has
an incompatible signature, class creation raises InterfaceError. You do not
need to wait until an instance is created or a method is called.
Why interface-contract?
Python already has abc.ABC and typing.Protocol; this package targets a
different boundary: strict runtime validation for plugin systems, application
architecture, dependency injection, and dynamically loaded code.
| Capability | abc.ABC |
runtime Protocol |
interface-contract |
|---|---|---|---|
| Missing method detected at runtime | instantiation | isinstance |
class definition |
| Runtime signature validation | no | no | yes |
| Property/static/class method kind validation | no | no | yes |
Signature-aware structural isinstance |
no | no | yes |
| Explicit default implementations | concrete method | concrete method | @default |
| Optional instance-field contracts | annotations only | presence only | presence + shallow type check |
| Runtime adapter registry | no | no | yes |
This is not a replacement for static typing. Use mypy or another type checker for whole-program analysis, and use interface-contract where runtime boundaries must fail loudly and predictably.
Core behavior
Definition-time validation
Concrete subclasses are checked as soon as their class statement executes.
Intermediate implementations can opt out until a concrete subclass is ready:
class BaseRepository(Repository, abstract=True):
def save(self, item: str) -> None:
pass
class MemoryRepository(BaseRepository):
def find(self, item_id: int) -> str:
return "row"
@property
def name(self) -> str:
return "memory"
Abstract implementations cannot be instantiated.
Default methods
Interface methods normally declare requirements and therefore must have an empty
body (..., pass, or a docstring-only body). Mark intentional implementations
with @default:
from interface_contract import Interface, default
class Named(Interface):
@property
def name(self) -> str: ...
@default
def display_name(self) -> str:
return self.name.title()
Structural interfaces
Set structural=True when inheritance is not under your control:
class Closable(Interface, structural=True):
def close(self) -> None: ...
class FileLike:
def close(self) -> None:
pass
assert isinstance(FileLike(), Closable)
assert issubclass(FileLike, Closable)
Unlike runtime-checkable protocols, the structural check also validates callable signatures and descriptor kinds.
Instance-field contracts
Field checking is opt-in, preserving compatibility with versions that ignored class annotations:
class UserRecord(Interface, check_attributes=True):
name: str
age: int
class User(UserRecord):
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
Fields are checked immediately after __init__. Standard annotations receive a
best-effort shallow runtime check; parameter contents such as every item inside
list[str] are intentionally not traversed. ClassVar does not declare an
instance field. Dataclass implementations are supported.
For objects that cannot inherit from an interface, use verify_instance or
satisfies:
from interface_contract import satisfies, verify_instance
verify_instance(User("Ada", 37), UserRecord) # returns the object or raises
assert satisfies(User("Ada", 37), UserRecord)
Adapters
The registry converts an existing type to a target interface and validates the result:
from interface_contract import AdapterRegistry
registry = AdapterRegistry()
@registry.register(dict, UserRecord)
def dict_to_user(data: dict[str, object]) -> User:
return User(name=str(data["name"]), age=int(data["age"]))
user = registry.adapt({"name": "Ada", "age": 37}, UserRecord)
adapt, can_adapt, register_adapter, and unregister_adapter expose a
process-wide default registry when a dedicated registry is unnecessary.
Optional annotation checks
Call signatures are always checked for parameter shape. To also compare available
parameter and return annotations, enable check_annotations=True:
class Parser(Interface, check_annotations=True):
def parse(self, value: str) -> int: ...
Runtime annotation comparison is deliberately conservative. It does not try to replace a static type checker.
Mypy integration
The package is typed and ships an optional mypy plugin. It lets mypy reject the instantiation of incomplete implementations before execution. No extra runtime dependency is installed.
[tool.mypy]
plugins = ["interface_contract.mypy_plugin"]
class Job(Interface):
def execute(self, payload: str) -> int: ...
class Incomplete(Job):
pass
Incomplete() # mypy: Cannot instantiate abstract class "Incomplete"
The mypy plugin API is itself experimental; runtime validation remains the source of truth.
Supported members
- regular and async methods
- properties, including independent getter/setter/deleter requirements
- static methods and class methods
- generic interfaces
- multiple and derived interfaces
- custom metaclass composition
- source-less environments such as REPL,
exec, notebooks, frozen apps, and bytecode-only distributions
Useful inspection functions include members_of, attributes_of,
missing_members, missing_attributes, signature_problem, verify, and
structurally_implements.
Backward compatibility
The former import path remains fully supported:
from strict_interface import Interface
strict_interface.Interface and interface_contract.Interface are the same
object. Existing source code does not need an import migration. The PyPI
distribution name is interface-contract; the preferred new import is
interface_contract.
Version 0.4.0 is additive except for the distribution rename. Runtime field
checking only activates when check_attributes=True is explicitly selected.
Development
python -m pip install -e ".[dev]"
python -m pytest
python -m ruff check .
python -m mypy strict_interface interface_contract typing_tests/valid.py
python -m build
python -m twine check dist/*
See README.tr.md for Turkish documentation and CHANGELOG.md for release notes.
License
MIT
Release files for interface-contract 0.4.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 | |
|---|---|---|---|
| interface_contract-0.4.0.tar.gz | 26.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| interface_contract-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:45.2 kB
Release files / interface_contract-0.4.0.tar.gz
| Download URL | interface_contract-0.4.0.tar.gz |
|---|---|
| Size | 26.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d7adcbf86c8a767d4dfd749b016d7e0b8be59f8e0b89343509c0c31bc673a12
|
|
BLAKE2b-256 checksum How to use checksums |
3006b1da22e698eb172bd0dbf0117258e24e172bd805f2d86a6d695bc1fbbf33
|
| 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 30, 2026.
Transparency logRelease files / interface_contract-0.4.0-py3-none-any.whl
| Download URL | interface_contract-0.4.0-py3-none-any.whl |
|---|---|
| Size | 18.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bcda8b2dfb82ba0c1285e41cda6c13920b4f79e828e7dca6b54c32760672fa71
|
|
BLAKE2b-256 checksum How to use checksums |
1b9290aef6dd221e93788ee8768e0cfc84895128794bf030f685bc8f4d9b0285
|
| 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 30, 2026.
Transparency log