# -*- coding: utf-8 -*-
from setuptools import setup

package_dir = \
{'': 'src'}

packages = \
['signpost']

package_data = \
{'': ['*']}

install_requires = \
['numpy>=1.8,<2.0', 'pandas>=0.17']

setup_kwargs = {
    'name': 'signpost',
    'version': '0.2.1',
    'description': 'Simple decorators and functions for type checking pandas DataFrames at runtime',
    'long_description': 'signpost\n========\n\n|pypi-version| |pypi-python-versions| |build-status| |coverage|\n\nThis is a simple library for annotating and enforcing properties of\npandas DataFrames at runtime. By showing which columns and types\nare expected before execution of a function begins, we can catch errors\nearlier with a message that makes sense and also document the inputs and\noutputs of functions more concisely.\n\nThis project is related to a number of others that all share similar goals.\nWhile perhaps stumbling straight headlong the Lisp curse, signpost is yet\nanother pandas validation library. Here is a list of other similar projects:\n\n* `Bulwark <https://github.com/zaxr/bulwark>`_\n* `pandera <https://github.com/pandera-dev/pandera>`_\n* `Table Enforcer <https://github.com/xguse/table_enforcer>`_\n* `pandas-validation <https://github.com/jmenglund/pandas-validation>`_\n* `PandasSchema <https://github.com/TMiguelT/PandasSchema>`_\n* `Opulent-Pandas <https://github.com/danielvdende/opulent-pandas>`_\n\nSo why reinvent the wheel? Signpost offers a few advantages:\n\n#. Support for delayed evaluation of property inputs through the use of ``Meta``.\n   This technique works especially well in settings where class variables may hold information\n   about the data being operated on by the class.\n#. Qualifiers allow for richer and more flexible descriptions of data\n#. Straightforward approach to function decorators that uses the same logic as Python itself\n   to match properties to DataFrames\n#. Strict Python type checking via mypy\n\n\nExample Usage\n-------------\n\nHere is an example of standard usage to decorate a fairly strange function.\nNote that any valid pandas index value can be used, including numbers. We\ncan combine ``Property``\'s together using ``And`` and ``Or`` if desired\nas well as qualify them using "all", "any", "just", or "none".\n\n.. code-block:: python\n\n    from signpost import Cols, Schema, Values, Superkey, And, df_args, df_return\n\n    @df_args(\n        Cols("all", ["thing_1", 2]) & Superkey("thing_1", over=2),\n        other=Schema("just", {"thing_2": int, "thing_3": "string"})\n    )\n    @df_return(\n        None,\n        And(\n            Cols("all", ["thing_1", "thing_2", "thing_3"]),\n            Values("any", {"thing_1": [1, 2], "thing_3": ["foo", "bar"]}),\n            Values("none", {"thing_1": [3]}),\n        )\n    )\n    def do_a_thing(df: pd.DataFrame, other: pd.DataFrame) -> Tuple[int, pd.DataFrame]:\n        ...\n\nHowever, there are times when the particular properties of a data frame depend on other\ninputs to a function. For example, a function may take a list of columns to subset\nby or a set of values to query with. This behavior is somewhat analogous to a function\ntaking a ``List[T]`` and a parameter of type ``T`` – we are essentially making the data\nframe generic over a parameter specified by the caller. In these cases, we can\nuse the ``Meta`` constructor, which is constructed with a string of Python code.\nThe code is then evaluated with the environment of the function.\nFor example, we can implement a checked "project" function\n(analogous to ``SELECT DISTINCT`` in SQL) as follows:\n\n.. code-block:: python\n\n    from signpost import df_args, df_return, Cols, Meta\n\n    @df_args(Cols("all", Meta("cols")))\n    @df_return(Cols("just", Meta("cols")))\n    def project(df: pd.DataFrame, cols: List[str]) -> pd.DataFrame:\n        return df.loc[:, cols].drop_duplicates()\n\nSince the expressions passed to these meta properties can be arbitrary Python strings,\nwe can express some fairly powerful logic using relatively little code. Note that\nsince pandas DataFrames are dict-like, we can treat them as sequences of column names.\n\n.. code-block:: python\n\n    from signpost import df_args, df_return, Cols, Meta\n\n    @df_args(left=Cols("any", Meta("right")), right=Cols("any", Meta("left")))\n    @df_return(Cols("just", Meta("set(left) | set(other)"))\n    def merge(left, right):\n        return pd.merge(left, right)\n\nOther use cases\n^^^^^^^^^^^^^^^\nFor usage inside scripts, it is useful to use the ``Checker`` inner class for various properties.\nFor example,\n\n.. code-block:: python\n\n    from signpost import Cols, Values\n\n    df = pd.read_csv("my_file.csv")\n    df = Cols.Checker("just", ["col_a", "col_b"]).validate(df)\n    df = Values.Checker("all", {"col_a": [1, 2], "col_b": [1, 1]}).validate(df)\n\nWhen combined with ``pd.DataFrame.pipe``, ``validate`` can provide expressive sanity checking.\nIf you would like more custom handling, you can use the ``check`` method as follows:\n\n.. code-block:: python\n\n    from signpost import Cols\n\n    df = ...\n    error: Optional[str] = Cols.Checker("just", ["col_a", "col_b"]).check(df)\n    if error is not None:\n        print(error)\n        # more handling\n        ...\n\n\nList of Properties\n------------------\n\n* Cols: checks that the specified columns are in the data\n* Schema: checks whether the specified column / data type pairs match the data\n* Values: enforces which values (and combinations of values) need to be present in the data\n* Superkey: checks that the specified columns uniquely identify the data\n* Notna: enforces that the specified columns do not contain NA / missing values\n* MergeResult: checks whether a merge was a inner, left, or right join\n* Bounded: enforces that the values in the specified columns fall between two (potentially unbounded) values\n\nSpecial properties\n^^^^^^^^^^^^^^^^^^\n* Function: wraps a bare function into a property, useful for quick checks\n* And: combines two properties into a new property that checks each in turn, stopping if an error is found\n* Or: combines two properties into a new property that checks each in turn, stopping once a property succeeds\n* Assume: wraps a property to always be true, useful for documenting a property without doing unnecessary computation\n\n\nInstallation\n------------\n\nInstallation is easy! Just type:\n\n.. code-block:: console\n\n    pip install signpost\n\nExtending signpost\n------------------\nThere are a couple of ways to extend signpost. The first is using the ``Function`` property.\nIt simply accepts a function that takes a pandas DataFrame and a context dictionary and returns\na ``Optional[str]``.\n\n.. code-block:: python\n\n    from signpost import df_args, Function\n\n    @df_args(Function(lambda df, context: "bad" if df.empty else None))\n    def do_another_thing(df: pd.DataFrame):\n        ...\n\nIt is also possible to create new ``Property``\'s simply by implementing the ``Property``\nor ``ContextProperty`` interface found in ``signpost.properties``.\n\n\n.. |pypi-version| image:: https://img.shields.io/pypi/v/signpost\n    :alt: PyPI\n    :target: https://pypi.org/project/signpost\n\n.. |pypi-python-versions| image:: https://img.shields.io/pypi/pyversions/signpost\n    :alt: PyPI - Python Version\n    :target: https://pypi.org/project/signpost\n\n.. |build-status| image:: https://travis-ci.com/ilsedippenaar/signpost.svg?branch=main\n    :alt: Build Status\n    :target: https://travis-ci.com/ilsedippenaar/signpost\n\n.. |coverage| image:: https://codecov.io/gh/ilsedippenaar/signpost/branch/main/graph/badge.svg\n    :alt: Code Coverage\n    :target: https://codecov.io/gh/ilsedippenaar/signpost\n',
    'author': 'Ilse Dippenaar',
    'author_email': 'ilsedipp@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/ilsedippenaar/signpost',
    'package_dir': package_dir,
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.6.1,<4.0.0',
}


setup(**setup_kwargs)
