Skip to content

Commit

Permalink
doc
Browse files Browse the repository at this point in the history
  • Loading branch information
ken-morel committed May 27, 2024
1 parent 693f243 commit 8dafdd6
Showing 1 changed file with 93 additions and 195 deletions.
288 changes: 93 additions & 195 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,203 +13,101 @@
.. image:: https://coveralls.io/repos/github/ken-morel/pyoload/badge.svg?branch=main
:alt: Coverage Status
:target: https://coveralls.io/github/ken-morel/pyoload?branch=mai
.. image:: https://img.shields.io/badge/stackoverflow-Ask%20questions-blue.svg
:target: https://stackoverflow.com/questions/tagged/pyoload
.. image:: https://readthedocs.org/projects/pyoload/badge/?version=latest
:target: https://pyoload.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
:target: https://pyoload.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

==================================================
=======
pyoload
==================================================

:python:`pyoload` provides an intuitive and easy way to add type and value checking
to function arguments and class attributes.

--------------------------------------------------
usage
--------------------------------------------------

:python:`pyoload` base provides two simple use functions

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:python:`pyoload.annotate`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:python:`pyoload.annotate` used as a decorator over a simple function
it returns a wrapper function which on each call
- get the function's annotations
- resolve the annotations if stringified, on error raises
a :python:`pyoload.AnnotationResolutionError`
- check for matches between the passed arguments and annotations with
the recursive :python:`pyoload.typeMatch` function
- if all matches, then calls the function, else raises a
:python:`pyoload.AnnotationError`
- if the return annotation specified then returns it else raises
an annotation error.

Example

>>> from pyoload import *
>>> from pathlib import Path
>>>
>>>
>>> @annotate
... def add_eof(eof: str, file: Cast(Path)) -> int:
... '''
... :param eof: the string to append
... :param file: the file to add content to
... :returns: the new file size
... '''
... data = file.read_text()
... return file.write_text(data + eof)
...
>>>
>>> print(add_eof)
<function add_eof at 0x017B2D48>
>>> print(add_eof.__pyod_annotate__)
<function add_eof at 0x0109D7F8>
>>> print(add_eof('@EOF@', 'del.txt'))
17


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:python:`pyoload.overload`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:python:`pyoload.overload` used as a decorator over a simple function
When decorating a function it:

- annotates the function with the special kwarg :python:`is_overload=True`
- gets the function's name using :python:`pyoload.get_name` and if needed
creates a new register dictionarry value in
:python:`pyoload.__overloads__[name]` and stores a copy in
the function's :python:`.__pyod_overloads__`

And on each call it simply loops through each function entry, while
it catches a :python:`pyoload.InternalAnnotationError` which is raised when
the special :python:`is_overload` is set to true

.. tip::

you may raise :python:`pyoload.InternalAnnotationError` inside an overloaded
function after carrying out some other checks and pyoload will switch to the
next oveload.

>>> from pyoload import *
>>> from pathlib import Path
>>>
>>>
>>> @overload
... def div(a: float|int, b: Checks(eq=0)):
... raise ZeroDivisionError()
...
checks={'eq': 0}
>>>
>>> @overload
... def div(a: int, b: int) -> int:
... return a // b
...
>>>
>>> @overload
... def div(a: float, b: float) -> float:
... return a / b
...
>>>
>>> @overload
... def div(a: Any, b: Any):
... raise NotImplementedError()
...
>>>
>>> print(div.__pyo_overloads__)
[<function div at 0x019C2D48>, <function div at 0x01B5EE38>, <function div at 0x01B65E88>, <function div at 0x01B65F78>]
>>> print(div.__pyod_overloads_name__)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute '__pyod_overloads_name__'. Did you mean: '__pyo_overloads_name__'?
>>>
>>> print(repr(div(1, 2)))
2
{'eq': 0} 2
<class 'pyoload.Check'> eq 0 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\pyoload\src\pyoload\__init__.py", line 399, in wrapper
val = f(*args, **kw)
^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 348, in wrapper
if not typeMatch(v, anno[k]):
^^^^^^^^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 225, in typeMatch
spec(val)
File "C:\pyoload\src\pyoload\__init__.py", line 146, in __call__
Check.check(name, params, val)
File "C:\pyoload\src\pyoload\__init__.py", line 72, in check
raise Check.CheckDoesNotExistError(name)
pyoload.Check.CheckDoesNotExistError: eq
>>> print(repr(div(1.0, 2.0)))
2.0
{'eq': 0} 2.0
<class 'pyoload.Check'> eq 0 2.0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\pyoload\src\pyoload\__init__.py", line 399, in wrapper
val = f(*args, **kw)
^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 348, in wrapper
if not typeMatch(v, anno[k]):
^^^^^^^^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 225, in typeMatch
spec(val)
File "C:\pyoload\src\pyoload\__init__.py", line 146, in __call__
Check.check(name, params, val)
File "C:\pyoload\src\pyoload\__init__.py", line 72, in check
raise Check.CheckDoesNotExistError(name)
pyoload.Check.CheckDoesNotExistError: eq
>>> print(repr(div(1.0, 2)))
2
{'eq': 0} 2
<class 'pyoload.Check'> eq 0 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\pyoload\src\pyoload\__init__.py", line 399, in wrapper
val = f(*args, **kw)
^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 348, in wrapper
if not typeMatch(v, anno[k]):
^^^^^^^^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 225, in typeMatch
spec(val)
File "C:\pyoload\src\pyoload\__init__.py", line 146, in __call__
Check.check(name, params, val)
File "C:\pyoload\src\pyoload\__init__.py", line 72, in check
raise Check.CheckDoesNotExistError(name)
pyoload.Check.CheckDoesNotExistError: eq
>>> print(repr(div('0', 0)))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\pyoload\src\pyoload\__init__.py", line 399, in wrapper
val = f(*args, **kw)
^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 360, in wrapper
ret = func(**vals)
^^^^^^^^^^^^
File "<stdin>", line 3, in div
NotImplementedError
>>> print(repr(div('0', 1j)))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\pyoload\src\pyoload\__init__.py", line 399, in wrapper
val = f(*args, **kw)
^^^^^^^^^^^^^^
File "C:\pyoload\src\pyoload\__init__.py", line 360, in wrapper
ret = func(**vals)
^^^^^^^^^^^^
File "<stdin>", line 3, in div
NotImplementedError





.. role:: python(code)
=======

pyoload has two main functions

----------------
pyoload.annotate
----------------

Is used as a decorator on the function.

.. code-block:: python
from pyoload import annotate
@annotate
def twice(a:int) -> int:
return a * 2
b = twice(4)
The annotate creates a wrapper over the decorated function which checks in for argument types over each function call using :python:`pyoload.matchType(val, spec)`.
The original function is kept in the :python:`.__pyod_annotate__` attribute.

----------------
pyoload.overload
----------------

Implements function overloading in python via a simple decorator

.. code-block:: python
from pyoload import overload
import math
cache = {}
tan_is_real = lambda v: not (v + 90) % 180 == 0
@overload
def tan(num:Validator(tan_is_real, opposite=True)):
raise ValueError(num)
@overload
def tan(num:int|float) -> float:
return math.tan(num(
tan(6)
When an overload is registerred, the function name in the form `functionModuleName.functionQualName` is goten using `pyoload.get_name(funcOrClass)` an annotate is gotten using `pyoload.annotate(func, True)`
and a new list of overloads is created and stored in `pyoload.__overloads__` dictionarry under it's name. A reference to the list of annotated overloads is stored in the functions `.__pyod_overloads__`.
When the function is called, the wrapper tries all the functions registerred to that name to catch a `pyoload.InternalAnnotationError`. If none ran succesfully, it raises an `pyoload.OverloadError`.
-------
Casting
-------
All `pyoload.annotate` and `pyoload.overload` both support Cast objects
instances of `pyoloas.Cast`.
It uses recursive casting with integrated support for dictionaries, e.g:
:python:`dict[int,tuple[list[float] | float]]`
for a dictionarry mapping of integers to list of floats or floats.
.. note::
When a union, e.g `int | str` is passed to Cast, it tries to cast in each of
the specified types in the listed order, that is
```python
cast = pyoload.Cast(int|str)
print(repr(cast(3i)))
```
Will print `'3i'` as `3i` can not be converted to a complex
--------------------
Accepted annotations
--------------------
In addition to supporting normal plain types,
pyoload includes support for generic aliasses of iterable types and some other classes:
- :python:`pyoload.Values(iterable)`
e.g `Values("+-*/")` or `Values(range(6))`
- :python:`pyoload.Cast(type)`
Instructs pyoload to cast to the specified type
- A string
The string contents will be evaluated as soon as first function call.
.. role:: python
:language: python
:syntax: python

0 comments on commit 8dafdd6

Please sign in to comment.