Skip to content

Commit

Permalink
Update the docs
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroSteiner committed Jul 10, 2023
1 parent 010aa5f commit 89d7b1d
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 69 deletions.
1 change: 1 addition & 0 deletions docs/source/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Version 4.0.0
* **Breaking:** Dropped support for Python versions 3.4 and 3.5
* **Breaking:** Invalid floating point literals now raise :py:exc:`~.errors.FloatSyntaxError` instead of
:py:exc:`~.errors.RuleSyntaxError`
* Added the new :py:class:`~rule_engine.types.DataType.FUNCTION` data type

Version 3.x.x
-------------
Expand Down
30 changes: 15 additions & 15 deletions docs/source/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ Basic Usage
+-----------+-----------------------+-----------------------------------+
| Attribute | Python Type | Rule Engine Type |
+-----------+-----------------------+-----------------------------------+
| title | ``str`` | :py:attr:`~ast.DataType.STRING` |
| title | ``str`` | :py:attr:`~.DataType.STRING` |
+-----------+-----------------------+-----------------------------------+
| publisher | ``str`` | :py:attr:`~ast.DataType.STRING` |
| publisher | ``str`` | :py:attr:`~.DataType.STRING` |
+-----------+-----------------------+-----------------------------------+
| issue | ``int`` | :py:attr:`~ast.DataType.FLOAT` |
| issue | ``int`` | :py:attr:`~.DataType.FLOAT` |
+-----------+-----------------------+-----------------------------------+
| released | ``datetime.date`` | :py:attr:`~ast.DataType.DATETIME` |
| released | ``datetime.date`` | :py:attr:`~.DataType.DATETIME` |
+-----------+-----------------------+-----------------------------------+

* An example comic book collection might look like:
Expand Down Expand Up @@ -180,7 +180,7 @@ Setting A Default Value
^^^^^^^^^^^^^^^^^^^^^^^
By default, :py:class:`engine.Rule` will raise a :py:class:`~errors.SymbolResolutionError` for invalid symbols. In some
cases, it may be desirable to change the way in which the language behaves to instead treat unknown symbols with a
default value (most often ``None`` / :py:attr:`ast.DataType.NULL` is used for this purpose, but any value of a supported
default value (most often ``None`` / :py:attr:`~.DataType.NULL` is used for this purpose, but any value of a supported
type can be used). To change this behavior, set the *default_value* parameter when initializing the
:py:class:`~engine.Context` instance.

Expand Down Expand Up @@ -249,7 +249,7 @@ type needs to be resolved. The return type should be a member of the :py:class:`
context = rule_engine.Context(type_resolver=type_resolver)
:py:attr:`~ast.DataType.UNDEFINED` can be defined as the data type for a valid symbol without specifying explicit type
:py:attr:`~.DataType.UNDEFINED` can be defined as the data type for a valid symbol without specifying explicit type
information. In this case, the rule object will know that it is a valid symbol, but will not validate any operations
that reference it.

Expand All @@ -275,18 +275,18 @@ In all cases, when a *type_resolver* is defined, the :py:class:`~engine.Rule` ob

Compound Data Types
"""""""""""""""""""
Compound data types such as the :py:class:`~ast.DataType.ARRAY` and :py:class:`~ast.DataType.MAPPING` types can
optionally specify member type information by calling their respective type. For example, an array of strings would be
defined as ``DataType.ARRAY(DataType.STRING)`` while a mapping with string keys and float values would be defined as
Compound data types such as the :py:attr:`~.DataType.ARRAY` and :py:attr:`~.DataType.MAPPING` types can optionally
specify member type information by calling their respective type. For example, an array of strings would be defined as
``DataType.ARRAY(DataType.STRING)`` while a mapping with string keys and float values would be defined as
``DataType.MAPPING(DataType.STRING, DataType.FLOAT)``. For more information, see the documentation for the
:py:meth:`~ast.DataType.ARRAY`, :py:meth:`~ast.DataType.MAPPING` functions.
:py:attr:`~.DataType.ARRAY`, :py:attr:`~.DataType.MAPPING` functions.

Compound member types can only be a single data type. In some cases the data type can optionally be nullable which means
that the member value can be either the specified type or :py:class:`~ast.DataType.NULL`. For example, a
:py:class:`~ast.DataType.MAPPING` type whose values are all nullable strings may be defined, while a
:py:class:`~ast.DataType.MAPPING` type with one value type of a :py:class:`~ast.DataType.STRING` and another of a
:py:class:`~ast.DataType.BOOLEAN` may not be defined. In this case, the key type may be defined while the value type is
set to :py:class:`~ast.DataType.UNDEFINED` which is the default value.
that the member value can be either the specified type or :py:attr:`~.DataType.NULL`. For example, a
:py:attr:`~.DataType.MAPPING` type whose values are all nullable strings may be defined, while a
:py:attr:`~.DataType.MAPPING` type with one value type of a :py:attr:`~.DataType.STRING` and another of a
:py:attr:`~.DataType.BOOLEAN` may not be defined. In this case, the key type may be defined while the value type is set
to :py:attr:`~.DataType.UNDEFINED` which is the default value.

Defining Types From A Dictionary
""""""""""""""""""""""""""""""""
Expand Down
16 changes: 7 additions & 9 deletions docs/source/rule_engine/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@ Classes

.. autoclass:: DataType
:members:
:exclude-members: ARRAY, FUNCTION, MAPPING, SET
:exclude-members: ARRAY, MAPPING, SET
:show-inheritance:

.. autoattribute:: ARRAY
:annotation:
.. automethod:: ARRAY

.. autoattribute:: BOOLEAN
:annotation:
Expand All @@ -43,20 +42,19 @@ Classes
.. autoattribute:: FLOAT
:annotation:

.. autoattribute:: FUNCTION
:annotation:
.. automethod:: FUNCTION

.. autoattribute:: MAPPING
:annotation:
.. automethod:: MAPPING

.. autoattribute:: NULL
:annotation:

.. autoattribute:: SET
:annotation:
.. automethod:: SET

.. autoattribute:: STRING
:annotation:

.. autoattribute:: TIMEDELTA
:annotation:

.. autoattribute:: UNDEFINED
2 changes: 2 additions & 0 deletions docs/source/syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ The following symbols are provided by default using the :py:meth:`~builtins.Buil
symbols can be accessed through the ``$`` prefix, e.g. ``$pi``. The default values can be overridden by defining a
custom subclass of :py:class:`~engine.Context` and setting the :py:attr:`~engine.Context.builtins` attribute.

.. _builtin-functions:

Functions
^^^^^^^^^

Expand Down
34 changes: 23 additions & 11 deletions docs/source/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ compatible with. For a information regarding supported operations, see the
| :py:attr:`~DataType.DATETIME` | :py:class:`datetime.date`, |
| | :py:class:`datetime.datetime` |
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.TIMEDELTA`| :py:class:`datetime.timedelta`|
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.FLOAT` | :py:class:`int`, |
| | :py:class:`float` |
| | :py:class:`decimal.Decimal` |
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.FUNCTION` | *anything callable* |
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.MAPPING` | :py:class:`dict` |
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.NULL` | :py:class:`NoneType` |
Expand All @@ -33,6 +33,8 @@ compatible with. For a information regarding supported operations, see the
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.STRING` | :py:class:`str` |
+-------------------------------+-------------------------------+
| :py:attr:`~DataType.TIMEDELTA`| :py:class:`datetime.timedelta`|
+-------------------------------+-------------------------------+

Compound Types
--------------
Expand All @@ -50,6 +52,8 @@ operations apply to the members of :py:attr:`~DataType.ARRAY` and :py:attr:`~Dat

FLOAT
-----
See :ref:`literal-float-values` for syntax.

Starting in :release:`3.0.0`, the ``FLOAT`` datatype is backed by Python's :py:class:`~decimal.Decimal` object. This
makes the evaluation of arithmetic more intuitive for the audience of rule authors who are not assumed to be familiar
with the nuances of binary floating point arithmetic. To take an example from the :py:mod:`decimal` documentation, rule
Expand All @@ -66,10 +70,18 @@ Since Python's :py:class:`~decimal.Decimal` values are not always equivalent to
``0.1 != Decimal('0.1')``) it's important to know that Rule Engine will coerce and normalize these values. That means
that while in Python ``0.1 in [ Decimal('0.1') ]`` will evaluate to ``False``, in a rule it will evaluate to ``True``
(e.g. ``Rule('0.1 in numbers').evaluate({'numbers': [Decimal('0.1')]})``). This also affects Python dictionaries that
are converted to Rule Engine ``MAPPING`` values. While in Python the value ``{0.1: 'a', Decimal('0.1'): 'a'}`` would
have a length of 2 with two unique keys, the same value once converted into a Rule Engine ``MAPPING`` would have a
length of 1 with a single unique key. For this reason, developers using Rule Engine should take care to not use compound
data types with a mix of Python :py:class:`float` and :py:class:`~decimal.Decimal` values.
are converted to Rule Engine :py:attr:`~DataType.MAPPING` values. While in Python the value
``{0.1: 'a', Decimal('0.1'): 'a'}`` would have a length of 2 with two unique keys, the same value once converted into a
Rule Engine :py:attr:`~DataType.MAPPING` would have a length of 1 with a single unique key. For this reason, developers
using Rule Engine should take care to not use compound data types with a mix of Python :py:class:`float` and
:py:class:`~decimal.Decimal` values.

FUNCTION
--------
Version :release:`4.0.0` added the :py:attr:`~DataType.FUNCTION` datatype. This can be used to make functions available
to rule authors. Rule Engine contains a few :ref:`builtin functions<builtin-functions>` that can be used by default.
Additional functions can be either added them to the evaluated object or by extending the builtin symbols. It is only
possible to call a function from within the rule text. Functions can no be defined as other data types can be.

TIMEDELTA
---------
Expand All @@ -82,8 +94,8 @@ such as "has it been 30 days since this thing happened?" or "how much time passe

The following mathematical operations are supported:

* adding a timedelta to a datetime (result is a datetime)
* adding a timedelta to another timedelta (result is a timedelta)
* subtracting a timedelta from a datetime (result is a datetime)
* subtracting a datetime from another datetime (result is a timedelta)
* subtracting a timedelta from another timedelta (result is a timedelta)
* Adding a timedelta to a datetime (result is a datetime)
* Adding a timedelta to another timedelta (result is a timedelta)
* Subtracting a timedelta from a datetime (result is a datetime)
* Subtracting a datetime from another datetime (result is a timedelta)
* Subtracting a timedelta from another timedelta (result is a timedelta)
67 changes: 33 additions & 34 deletions lib/rule_engine/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ def __init__(self, name, python_type):
self.name = name
self.python_type = python_type
self.is_scalar = True
if '__call__' in dir(self) and self.__call__.__doc__:
# patch the call docs into the top-level class for Sphinx
self.__class__.__doc__ = self.__call__.__doc__

@property
def is_iterable(self):
Expand All @@ -202,7 +205,12 @@ def __repr__(self):
def is_compound(self):
return not self.is_scalar

_DATA_TYPE_UNDEFINED = _DataTypeDef('UNDEFINED', errors.UNDEFINED)
class _UndefinedDataTypeDef(_DataTypeDef):
def __repr__(self):
return 'UNDEFINED'

_DATA_TYPE_UNDEFINED = _UndefinedDataTypeDef('UNDEFINED', errors.UNDEFINED)

class _CollectionDataTypeDef(_DataTypeDef):
__slots__ = ('value_type', 'value_type_nullable')
def __init__(self, name, python_type, value_type=_DATA_TYPE_UNDEFINED, value_type_nullable=True):
Expand All @@ -223,6 +231,10 @@ def iterable_type(self):
return self.value_type

def __call__(self, value_type, value_type_nullable=True):
"""
:param value_type: The type of the members.
:param bool value_type_nullable: Whether or not members are allowed to be :py:attr:`.NULL`.
"""
return self.__class__(
self.name,
self.python_type,
Expand Down Expand Up @@ -275,6 +287,11 @@ def iterable_type(self):
return self.key_type

def __call__(self, key_type, value_type=_DATA_TYPE_UNDEFINED, value_type_nullable=True):
"""
:param key_type: The type of the mapping keys.
:param value_type: The type of the mapping values.
:param bool value_type_nullable: Whether or not mapping values are allowed to be :py:attr:`.NULL`.
"""
return self.__class__(
self.name,
self.python_type,
Expand Down Expand Up @@ -325,6 +342,17 @@ def __init__(self, name, python_type, value_name=None, return_type=_DATA_TYPE_UN
self.minimum_arguments = minimum_arguments

def __call__(self, name, return_type=_DATA_TYPE_UNDEFINED, argument_types=_DATA_TYPE_UNDEFINED, minimum_arguments=None):
"""
.. versionadded:: 4.0.0
:param str name: The name of the function, e.g. "split".
:param return_type: The data type of the functions return value.
:param tuple argument_types: The data types of the functions arguments.
:param int minimum_arguments: The minimum number of arguments the function requires.
If *argument_types* is specified and *minimum_arguments* is not, then *minimum_arguments* will default to the length
of *argument_types* effectively meaning that every defined argument is required. If
"""
return self.__class__(
self.name,
self.python_type,
Expand Down Expand Up @@ -404,43 +432,14 @@ class DataType(metaclass=DataTypeMeta):
This checks that the types are compatible without any kind of conversion. When dealing with compound data types,
this ensures that the member types are either the same or :py:attr:`~.UNDEFINED`.
"""
ARRAY = _ArrayDataTypeDef('ARRAY', tuple)
"""
.. py:function:: __call__(value_type, value_type_nullable=True)
:param value_type: The type of the array members.
:param bool value_type_nullable: Whether or not array members are allowed to be :py:attr:`.NULL`.
"""
ARRAY = staticmethod(_ArrayDataTypeDef('ARRAY', tuple))
BOOLEAN = _DataTypeDef('BOOLEAN', bool)
DATETIME = _DataTypeDef('DATETIME', datetime.datetime)
FLOAT = _DataTypeDef('FLOAT', decimal.Decimal)
FUNCTION = _FunctionDataTypeDef('FUNCTION', _PYTHON_FUNCTION_TYPE)
"""
.. py:function:: __call__(name, return_type=_DATA_TYPE_UNDEFINED, argument_types=_DATA_TYPE_UNDEFINED, minimum_arguments=None)
.. versionadded:: 4.0.0
:param str name: The name of the function, e.g. "split".
:param return_type: The type of the functions return value.
:param tuple argument_types: The types of the functions arguments.
:param int minimum_arguments: The minimum number of arguments the function requires.
"""
MAPPING = _MappingDataTypeDef('MAPPING', dict)
"""
.. py:function:: __call__(key_type, value_type, value_type_nullable=True)
:param key_type: The type of the mapping keys.
:param value_type: The type of the mapping values.
:param bool value_type_nullable: Whether or not mapping values are allowed to be :py:attr:`.NULL`.
"""
FUNCTION = staticmethod(_FunctionDataTypeDef('FUNCTION', _PYTHON_FUNCTION_TYPE))
MAPPING = staticmethod(_MappingDataTypeDef('MAPPING', dict))
NULL = _DataTypeDef('NULL', NoneType)
SET = _SetDataTypeDef('SET', set)
"""
.. py:function:: __call__(value_type, value_type_nullable=True)
:param value_type: The type of the set members.
:param bool value_type_nullable: Whether or not set members are allowed to be :py:attr:`.NULL`.
"""
SET = staticmethod(_SetDataTypeDef('SET', set))
STRING = _DataTypeDef('STRING', str)
TIMEDELTA = _DataTypeDef('TIMEDELTA', datetime.timedelta)
UNDEFINED = _DATA_TYPE_UNDEFINED
Expand Down
2 changes: 2 additions & 0 deletions tests/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ def test_data_type_function(self):

def test_data_type_definitions_describe_themselves(self):
for name in DataType:
if name == 'UNDEFINED':
continue
data_type = getattr(DataType, name)
self.assertRegex(repr(data_type), 'name=' + name)

Expand Down

0 comments on commit 89d7b1d

Please sign in to comment.