Metadata-Version: 2.1
Name: polyforce
Version: 0.2.0
Summary: Enforce annotations in your python code
Project-URL: Homepage, https://github.com/tarsil/polyforce
Project-URL: Documentation, https://polyforce.tarsild.io
Project-URL: Changelog, https://polyforce.tarsild.io/release-notes/
Project-URL: Funding, https://github.com/sponsors/tarsil
Project-URL: Source, https://github.com/tarsil/polyforce
Author-email: Tiago Silva <tiago.arasilva@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: polyforce
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: AnyIO
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: orjson<5.0,>=3.9.9
Provides-Extra: dev
Requires-Dist: ipdb<1.0.0,>=0.13.13; extra == 'dev'
Requires-Dist: pre-commit<4.0.0,>=3.3.1; extra == 'dev'
Provides-Extra: doc
Requires-Dist: mdx-include<2.0.0,>=1.4.1; extra == 'doc'
Requires-Dist: mkautodoc<0.3.0,>=0.2.0; extra == 'doc'
Requires-Dist: mkdocs-markdownextradata-plugin<0.3.0,>=0.1.7; extra == 'doc'
Requires-Dist: mkdocs-material==9.1.5; extra == 'doc'
Requires-Dist: mkdocs<2.0.0,>=1.4.2; extra == 'doc'
Requires-Dist: mkdocstrings<0.21.0,>=0.19.0; extra == 'doc'
Requires-Dist: pyyaml<7.0.0,>=5.3.1; extra == 'doc'
Provides-Extra: test
Requires-Dist: autoflake<3.0.0,>=2.0.2; extra == 'test'
Requires-Dist: black<24.0.0,>=23.3.0; extra == 'test'
Requires-Dist: isort<6.0.0,>=5.12.0; extra == 'test'
Requires-Dist: mypy<2.0.0,>=1.1.0; extra == 'test'
Requires-Dist: pydantic>=2.4.0; extra == 'test'
Requires-Dist: pytest-cov<5.0.0,>=4.0.0; extra == 'test'
Requires-Dist: pytest<8.0.0,>=7.2.2; extra == 'test'
Requires-Dist: requests>=2.28.2; extra == 'test'
Requires-Dist: ruff<1.0.0,>=0.0.256; extra == 'test'
Description-Content-Type: text/markdown

# Polyforce

<p align="center">
  <a href="https://polyforce.tarsild.io"><img src="https://res.cloudinary.com/tarsild/image/upload/v1696959172/packages/polyforce/logo_pyynl9.png" alt='Polyforce'></a>
</p>

<p align="center">
    <em>🔥 Enforce static typing in your codebase at runtime 🔥</em>
</p>

<p align="center">
<a href="https://github.com/tarsil/polyforce/workflows/Test%20Suite/badge.svg?event=push&branch=main" target="_blank">
    <img src="https://github.com/tarsil/polyforce/workflows/Test%20Suite/badge.svg?event=push&branch=main" alt="Test Suite">
</a>

<a href="https://pypi.org/project/polyforce" target="_blank">
    <img src="https://img.shields.io/pypi/v/polyforce?color=%2334D058&label=pypi%20package" alt="Package version">
</a>

<a href="https://pypi.org/project/polyforce" target="_blank">
    <img src="https://img.shields.io/pypi/pyversions/polyforce.svg?color=%2334D058" alt="Supported Python versions">
</a>
</p>

---

**Documentation**: [https://polyforce.tarsild.io][polyforce] 📚

**Source Code**: [https://github.com/tarsil/polyforce](https://github.com/tarsil/polyforce)

---

## Motivation

During software development we face issues where we don't know what do pass as specific parameters
or even return of the functions itself.

Tools like [mypy][mypy] for example, allow you to run static checking in your code and therefore
allowing to type your codebase properly but **it does not enforce it when running**.

For those coming from hevily static typed languages like **Java**, **.net** and many others, Python
can be overwhelming and sometimes confusing because of its versatility.

**Polyforce** was created to make sure you:

* Don't forget to type your functions and variables.
* Validates the typing in **runtime**.
* Don't forget thr return annotations.

Wouldn't be cool to have something like this:

> What if my function that expects a parameter of type string, if something else is passed could
simply fail, as intended?

This is where **Polyforce enters**.

## The library

Polyforce was designed to enforce the static typing **everywhere** in your code base. From functions
to parameters.

It was also designed to make sure the typing is enforced at runtime.

In other words, if you declare a type `string` and decide to pass an `integer`, it will blow throw
and intended error.

The library offers two ways of implementing the solution.

* [Via model](./model.md)
* [Via decorator](./decorator.md)

## Installation

```shell
$ pip install polyforce
```

## How to use it

Let us see some scenarios where the conventional python is applied and then where **Polyforce**
can make the whole difference for you.

### Conventional Python

Let us start with a simple python function.

#### Simple function

```python
def my_function(name: str):
    return name
```

In the normal python world, this wouldn't make any difference, and let us be honest, if you don't care
about mypy or any related tools, this will work without any issues.

This will also allow this to run without any errors:

```python
my_function("Polyfactory") # returns "Polyfactory"
my_function(1) # returns 1
my_function(2.0) # returns 2.0
```

The example above is 100% valid for that specific function and all values passed will be returned
equaly valid and the reson for this is because Python **does not enforce the static typing** so
the `str` declared for the parameter `name` **is merely visual**.

#### With objects

```python
class MyClass:

    def my_function(self, name: str):
        return name
```

And then this will be also valid.

```python
my_class = MyClass()

my_class.my_function("Polyfactory") # returns "Polyfactory"
my_class.my_function(1) # returns 1
my_class.my_function(2.0) # returns 2.0
```

I believe you understand the gist of what is being referred here. So, what if there was a solution
where we actually enforce the typing at runtime? Throw some errors when something is missing from
the typing and also when the wrong type is being sent into a function?

Enters [Polyforce](#polyforce)

### Polyforce

Now, let us use the same examples used before but using **Polyforce** and see what happens?

#### Simple function

```python hl_lines="1"
from polyforce import polycheck


@polycheck()
def my_function(name: str):
    return name
```

The example above it will throw a `ReturnSignatureMissing` or a `MissingAnnotation`
because the **missing return annotation** of the function or a parameter annotation respectively.

```python
my_function("Polyforce") # Throws an exception
```

The correct way would be:

```python hl_lines="1 5"
from polyforce import polycheck


@polycheck()
def my_function(name: str) -> str:
    return name
```

So what if now you pass a value that is not of type string?

```python
my_function(1) # Throws an exception
```

This will also throw a `TypeError` exception because you are trying to pass a type `int` into a
declared type `str`.

#### With objects

The same level of validations are applied within class objects too.

```python hl_lines="1 4"
from polyforce import PolyModel


class MyClass(PolyModel):

    def __init__(self, name, age: int):
        ...

    def my_function(self, name: str):
        return name
```

The example above it will throw a `ReturnSignatureMissing` and a `MissingAnnotation`
because the **missing return annotation for both __init__ and the function** as well as the missing
types for the parameters in both.

The correct way would be:

```python hl_lines="1 4"
from polyforce import PolyModel


class MyClass(PolyModel):

    def __init__(self, name: str, age: int) -> None:
        ...

    def my_function(self, name: str) -> str:
        return name
```

## The Polyforce

As you can see, utilising the library is very simple and very easy, in fact, it was never so easy to
enforce statuc typing in python.

For classes, you simply need to import the `PolyModel`.

```python
from polyforce import PolyModel
```

And to use the decorator you simply can:

```python
from polyforce import polycheck
```

## PolyModel vs polycheck

When using `PolyModel`, there is no need to apply the `polycheck` decorator. The `PolyModel` is
smart enough to apply the same level of validations as the `polycheck`.

When using the `PolyModel` you can use normal python as you would normally do and that means
`classmethod`, `staticmethod` and normal functions.

This like this, the `polycheck` is used for all the functions that are not inside a class.

## Limitations

For now, **Polyforce** is not looking at **native magic methods** (usually start and end with double underscore).
In the future it is planned to understand those on a class level.

[polyforce]: https://polyforce.tarsild.io
[mypy]: https://mypy.readthedocs.io/en/stable/
