interface-py 1.5.0
A package to define interface in Python
Requires Python: >=3.6

# interface-py

**interface-py** is a lightweight Python package for defining **interfaces** and **concrete implementations** with enforced contracts.  
It ensures that concrete classes implement all required methods, properties, and **fields**, including validation for empty or invalid property getters.

---

## Features

- Define **interfaces** using the `@interface` decorator.
- Enforce that concrete classes implement all interface methods, fields, and properties.
- Detects and enforces rules for **empty**, **explicit**, and **invalid getters/setters**.
- Support for **fields** with four declaration styles:
  1. With annotation only → `x: int`
  2. With annotation + `...` → `y: float = ...`
  3. Without annotation + `...` → `z = ...`
  4. With direct type assignment → `DataModel = dict` or any other type/class
- Ensures that **property getters** defined explicitly in interfaces always raise an error.
- Allows defining properties in interfaces with empty bodies (`...`, `pass`, or docstring-only`).
- Enforce **getter**, **setter**, and **deleter** implementation rules for properties.
- Supports **multi-level interface hierarchies** with automatic **contract aggregation**.
- Prevents runtime errors from missing implementations.
- Works alongside Python's built-in ABCs.

---

## Installation

```bash
pip install interface-py
```

---

## Usage

### Defining an Interface

```python
from interface_py import interface

@interface
class HumanInterface:
    # field definitions
    name: str
    age: int = ...
    nickname = ...
    DataModel = dict  # direct type assignment
    
    def speak(self): ...
    
    @property
    def rank(self): ...
    
    @rank.setter
    def rank(self, value): ...
```

- Property definitions in interfaces may have an **empty body** (`...`, `pass`, or docstring-only`).
- However, **explicit getters** (e.g. `@rank.getter`) are **not allowed** in interfaces, even if their body is empty.

---

# 🦆 Duck Typing (Structural Compatibility Checks)

`interface-py` provides **structural compatibility checks** via a separate submodule:  
`interface_py.duck_typing`

This module lets you check whether a class or instance **matches an interface contract structurally**,  
even if it doesn’t explicitly inherit from it.

---

## Example

```python
from interface_py import interface
from interface_py.duck_typing import is_duck_subclass, is_duck_instance

@interface
class FlyableInterface:
    def fly(self): ...

class Bird:
    def fly(self):
        print("Flying high!")

class Plane:
    def fly(self):
        print("Taking off!")

# Structural (duck) compatibility check
assert is_duck_subclass(Bird, FlyableInterface)
assert is_duck_instance(Plane(), FlyableInterface)
```

✅ In this example:
- Neither `Bird` nor `Plane` explicitly inherit from `FlyableInterface`.  
- However, both provide the required `fly()` method.
- Therefore, they are **structurally compatible** with the interface.  
  (“If it flies like a bird, it’s a bird.”)

---

## Notes

- These checks are **opt-in** and **non-intrusive** —  
  They don’t affect Python’s built-in `issubclass()` or `isinstance()` behavior.  
  Instead, you explicitly call `is_duck_subclass()` or `is_duck_instance()`.
- Works with:
  - Methods  
  - Properties  
  - Fields  
  - Static methods  
  - Class methods  
- This design avoids modifying the MRO or inheritance hierarchy.
- Future versions may extract this logic into a standalone package for broader reuse.

---

## Import Path

```python
from interface_py.duck_typing import (
    is_duck_subclass,
    is_duck_instance,
)
```

---

### Multi-level Interface Example

```python
from interface_py import interface, concrete

@interface
class MilitaryHumanInterface(HumanInterface):
    def march(self): ...

@concrete
class Soldier(MilitaryHumanInterface):
    name: str = "John"
    age: int = 25
    nickname = "Eagle"
    
    def speak(self):
        print("Reporting for duty!")

    def march(self):
        print("Marching!")

    @property
    def rank(self):
        return self._rank
    
    @rank.setter
    def rank(self, value):
        self._rank = value
```

- `MilitaryHumanInterface` **extends** `HumanInterface`.  
- `Soldier` **implements all required methods, fields, and properties** from both interfaces automatically.  
- Multi-level inheritance automatically merges all parent interface contracts.

---

## Field Enforcement Examples

```python
from interface_py import interface, concrete

@interface
class ExampleInterface:
    x: int              # only annotation
    y: float = ...      # annotation with ellipsis
    z = ...             # plain ellipsis
    DataModel = dict    # direct type assignment


# ✅ Correct implementation
@concrete
class GoodImpl(ExampleInterface):
    x: int = 10
    y: float = 3.14
    z = "hello"
    DataModel = dict


# ❌ Incorrect implementation
@concrete
class BadImpl(ExampleInterface):
    x: str = "oops"   # wrong type (expected int)
    # y missing → TypeError
    z = ...           # not allowed to keep ellipsis
    DataModel = list   # wrong type assignment
```

---

## Method Enforcement Rules

In **interfaces**, all methods must have an **empty body**.  
Empty bodies are recognized as:
- `pass`
- `...`
- docstring-only functions (for example, a function that only contains a string literal as its body)

In **concrete classes**, all methods must have a **non-empty body**.  
Methods defined with only `pass`, `...`, or docstring-only are considered **unimplemented** and raise a `TypeError`.

---

## Property Enforcement Rules

- A property can be declared in an interface as:

  ```python
  @property
  def data(self): ...
  ```

- The above is valid and treated as a contract placeholder.  
- However, the following is **invalid** and raises an error:

  ```python
  @property
  def data(self):
      pass

  @data.getter
  def data(self):
      ...
  ```

- Concrete classes **must** implement the getter, and if defined in the interface, also the setter and deleter.

---

## Validation

- Instantiating a concrete class that **does not implement all interface methods/fields/properties** raises a `TypeError`.
- Ensures consistent **interface contracts** across your project.
- The decorator `@interface` automatically enforces the interface behavior without requiring any base class.
- The decorator `@concrete` ensures that at least one parent of the concrete class is an interface.

---

## Why Use interface-py?

- Provides **contract enforcement** in dynamically typed Python.
- Detects incomplete or empty implementations early.
- Helps structure large codebases with clear **interface and implementation separation**.
- Avoids runtime errors from missing or placeholder methods.
- Enforces correctness while remaining Pythonic.

---

## License

MIT License