Metadata-Version: 2.4
Name: ihoop
Version: 0.1.2
Summary: Abstract/Final Patterns in Python
Author: lockwo
Project-URL: repository, https://github.com/lockwo/ihoop
Keywords: python,metaclasses,typing,concrete,strict
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: examples
Requires-Dist: equinox>=0.13.0; extra == "examples"
Provides-Extra: testing
Requires-Dist: pytest>=7.4.0; extra == "testing"
Requires-Dist: nbmake>=1.5.0; extra == "testing"
Requires-Dist: pyright==1.1.408; extra == "testing"
Dynamic: license-file


<h1 align="center">ihoop</h1>
<h2 align="center">Strict, immutable Abstract/Final classes</h2>

Do you like python, but wish it is was more like Julia? Do like Julia, but wish it was a language other people used? Did you read *Design Patterns: Elements of Reusable Object-Oriented Software*, but wish you didn't? 

Meet `ihoop`: a tiny library that turns plain Python classes into frozen, Abstract/Final instances. Drop in `Strict` as a base class and get:
- Immutability by default: once `__init__` finishes, your objects are read-only.  
- Abstract or Final:
  - Abstract classes: can be subclassed, but cannot be instantiated.  
  - Concrete classes: can be instantiated, but cannot be subclassed.  
- Abstract attributes, not just methods: Declare with `AbstractAttribute[T]`; subclasses must supply (via type hints) a real value.
- Abstract class variables: Declare with `AbstractClassVar[T]`; subclasses must supply a value, which becomes immutable.
- No dependencies: pure Python.


This package was largely ~~copied~~ inspired by [equinox](https://docs.kidger.site/equinox/)'s `strict=True` flag. For more information see https://docs.kidger.site/equinox/pattern/.

Why is it called `ihoop`? Because (i) (h)ate (o)bject (o)riented (p)rogramming 😉

## Example

```python
from ihoop import Strict, AbstractAttribute, AbstractClassVar

class AbstractAnimal(Strict):
    name: AbstractAttribute[str]
    species: AbstractClassVar[str]

class Dog(AbstractAnimal):
    name: str
    species = "Canis familiaris"

    def __init__(self, name: str):
        self.name = name

>>> Dog("Fido").name
'Fido'
>>> Dog.species
'Canis familiaris'
>>> d = Dog("Rex")
>>> d.name = "Max"  # Instance attributes are immutable
AttributeError: Cannot set attribute 'name' on frozen instance of Dog. strict objects are immutable after initialization.
>>> Dog.species = "Something else"  # Class variables are also immutable once resolved
AttributeError: Cannot set frozen class variable 'species' on class 'Dog'. Class variables resolved from abstract requirements are immutable.
```

## Sharp Edges

- It is important to type hint all member variables at the class level. It is very possible to bypass the checking/enforcement of `Strict` by doing things in the `__init__`.

## Roadmap

- [x] dataclass testing and integration
- [x] abstractclassvar: strictness for class variables
- [x] Package for pypi
- [ ] ?
