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

packages = \
['chili']

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

setup_kwargs = {
    'name': 'chili',
    'version': '1.3.1',
    'description': 'Chili is a dataclass support library. It is providing simple and fast hydration and extraction interfaces for dataclasses.',
    'long_description': '# Chili [![codecov](https://codecov.io/gh/kodemore/chili/branch/main/graph/badge.svg?token=TCG7SRQFD5)](https://codecov.io/gh/kodemore/chili) [![CI](https://github.com/kodemore/chili/actions/workflows/main.yaml/badge.svg?branch=main)](https://github.com/kodemore/chili/actions/workflows/main.yaml) [![Release](https://github.com/kodemore/chili/actions/workflows/release.yml/badge.svg)](https://github.com/kodemore/chili/actions/workflows/release.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\nChili is an extensible dataclass support library.\nIt contains helper functions to simplify initialising and extracting complex dataclasses.\nThis might come in handy when you want to transform your request\'s data to well-defined and easy to understand objects \nor when there is a need to hydrate and/or transform database records to desired representation in memory.\n\nLibrary also ensures type integrity and provides simple interface, which does not pollute your codebase\nwith unwanted abstractions.\n\n## Features\n- extensible and easy to use\n- initialisation and extraction for complex dataclasses\n- supports most python\'s types found in `typing` package (including generics)\n- does not pollute your codebase\n- supports adding custom types\n- data mapping/transformation with `chili.Mapping`\n\n## Installation\n\nWith pip,\n```shell\npip install chili\n```\nor through poetry\n```shell\npoetry add chili\n```\n\n# Usage\n\n## Initialising a dataclass\n\n```python\nfrom dataclasses import dataclass\nfrom typing import List\nfrom chili import init_dataclass\n\n@dataclass\nclass Tag:\n    id: str\n    name: str\n\n@dataclass\nclass Pet:\n    name: str\n    tags: List[Tag]\n    age: int\n\npet = init_dataclass({"name": "Bobik", "tags": [{"name": "dog", "id": "12"}]}, Pet)\nassert isinstance(pet, Pet)\nassert isinstance(pet.tags, List)\nassert isinstance(pet.tags[0], Tag)\n```\n\n> This example shows how simply cast your dict to given dataclass and that type are ensured by the init_dataclass function.\n\n## Transforming dataclass back to a dict\n\n```python\nfrom chili import asdict\nfrom dataclasses import dataclass\n\n@dataclass\nclass Money:\n    currency: str\n    amount: float\n\nsome_pounds = Money("GBP", "100.00")\n\nsome_pounds_dict = asdict(some_pounds)\n\nassert isinstance(some_pounds_dict, dict)\nassert isinstance(some_pounds_dict["amount"], float)\n```\n\n> Chili works with wide commonly used python types, but not every type can be simply transformed back and forth, \n> so make sure you familiarise yourself with supported types.\n\n## Using default values\n\n```python\nfrom dataclasses import dataclass, field\nfrom typing import List\nfrom chili import init_dataclass\n\n\n@dataclass\nclass Pet:\n    name: str\n    tags: List[str] = field(default_factory=lambda: ["pet"])\n\n\nboo = init_dataclass({"name": "Boo"}, Pet)\n\nassert isinstance(boo, Pet)\nassert boo.tags == [\'pet\']\n```\n\nIn the above example tags attribute was not available in the dict object, so default value set \nin dataclass is being used instead.\n\n\n> Please note `dataclasses` module does not allow mutable values in `default` argument of the `field` function, \n> so this example is using `default_factory` instead. More details about dataclasses\' `default` and `default_factory` \n> arguments are available in the [python\'s documentation](https://docs.python.org/3/library/dataclasses.html#dataclasses.field).\n\n## Hiding fields from hydration/deserialisation\n\nThere might be scenarios where not all dataclass\' fields should be hydrated. In this scenario use built-in python\'s `dataclasses` module `field` function, like in the example below:\n\n```python\nfrom chili import init_dataclass\nfrom dataclasses import dataclass, field\nfrom typing import List\n\n\n@dataclass\nclass Pet:\n    name: str\n    tags: List[str]\n    tags_length: int = field(init=False)\n\n    def __post_init__(self):\n        self.tags_length = len(self.tags)\n\n\nboo = init_dataclass({"name": "Boo", "tags": ["hamster", "boo"], "tags_length": 0}, Pet)\n\nassert isinstance(boo, Pet)\nassert boo.tags_length == 2\n```\n\nIn the above example length of the `tags` attribute is recalculated everytime we initialise the class \nand hydrating it might be superfluous.\n\n\n## Hiding fields from extraction/serialisation\n\nTo hide attributes of dataclass from being extracted into dict simply use `field` function with `repr` attribute set to `False`\n\n```python\nfrom dataclasses import dataclass, field\nfrom typing import List\n\nfrom chili import asdict\n\n\n@dataclass\nclass Pet:\n    name: str\n    tags: List[str] = field(repr=False)\n\n\nboo = Pet(name="Boo", tags=["pet", "hamster", "powerful!"])\n\nboo_dict = asdict(boo)\n\nassert "tags" not in boo_dict\n```\n\n## Data mapping\n\nSometimes you might run into scenarios that data coming from different sources needs to be remapped \nbefore you can hydrate it to your dataclass. There might be several reasons for that:\n- input data is using camelCase convention\n- input data is using different naming\n- input data is missing values\n\nIn all those cases you can pass `mapping` attribute to `init_dataclass/hydrate` or `asdict/extract` functions to perform\nmapping before hydration or after extraction dataclass.\n\n### Simple mapping\nPlease consider the following example of simple name mapping:\n\n```python\nfrom dataclasses import dataclass\nfrom typing import List\n\nimport chili\n\ninput_data = {\n    "petName": "Bobik",\n    "age": "12",\n    "taggedWith": [\n        {"tagName": "smart"},\n        {"tagName": "dog"},\n        {"tagName": "happy"},\n    ]\n}\n\n\n@dataclass\nclass Pet:\n    name: str\n    age: int\n    tags: List[dict]\n\n\nmapping = chili.Mapper({\n    "name": "petName",  # `petName` will be renamed to `name`, which corresponds to `Pet.name` field\n    "age": True,  # we just pass true value to include field "as is"\n    "tags": chili.KeyMapper("taggedWith", {  # `taggedWith` is a complex structure we want to map, so we have to use KeyMapper \n        "name": "tagName",  # `tagName` will be renamed to `name` which corresponds to `Pet.tags[{index}].name`\n    }),\n})\n\nbobik = chili.hydrate(input_data, Pet, mapping=mapping)\nprint(bobik)  # Pet(name=\'Bobik\', age=12, tags=[{\'name\': \'smart\'}, {\'name\': \'dog\'}, {\'name\': \'happy\'}])\n```\n\n### Mappings with custom behaviour\n\nWe can also use lambdas and functions in mapping to achieve the same result as in the previous example.\n\n```python\nfrom dataclasses import dataclass\nfrom typing import List, Tuple\n\nimport chili\n\n\ndef map_pet_tags(value: List) -> List:\n    return [{"name": item["tagName"]} for item in value]\n\n\ninput_data = {\n    "petName": "Bobik",\n    "petAge": "12",\n    "taggedWith": [\n        {"tagName": "smart"},\n        {"tagName": "dog"},\n        {"tagName": "happy"},\n    ]\n}\n\n\n@dataclass\nclass Pet:\n    name: str\n    age: int\n    tags: List[dict]\n\n\nmapping = chili.Mapper({\n    "name": "petName",\n    "age": lambda value: value["petAge"],  # callables will always receive current\'s scope data as input \n    "tags": chili.KeyMapper("taggedWith", map_pet_tags)\n})\n\nbobik = chili.hydrate(input_data, Pet, mapping=mapping)\nprint(bobik)  # Pet(name=\'Bobik\', age=12, tags=[{\'name\': \'smart\'}, {\'name\': \'dog\'}, {\'name\': \'happy\'}])\n```\n\n## Declaring custom hydrators\n\nIf you work with types that are neither dataclasses nor directly supported by `Chili`, you can define your own\nhydrator to customise how the type is initialised and how it should be de-initialised by declaring a subclass of\n`chili.hydration.HydrationStrategy` and registering it, like below:\n\n```python\nfrom chili import hydrate, registry, extract, HydrationStrategy\nimport  typing\n\nclass MyType:\n    def __init__(self, value):\n        self.value = value\n\nclass MyHydrator(HydrationStrategy):\n    def extract(self, value):  # value will be instance of MyType\n        return value.value\n        \n    def hydrate(self, value):\n        return MyType(value)\n\n# register our custom type in the hydration registry    \nregistry.add(MyType, MyHydrator())\n\n# usage\nassert isinstance(hydrate("hello", MyType), MyType)\nassert isinstance(hydrate("hello", typing.Optional[MyType]), MyType) # this will work as well with optional types\n\nassert extract(MyType("hello")) == "hello"\n```\n\n\n## Working with `Generic` types\n\n`Chili` support most of python\'s generic types like; `typing.List`, `typing.Tuple`, `typing.Dict`, etc. \nSupport is also provided for generic types defined by user (to some extent).\n\n```python\nfrom dataclasses import dataclass\nfrom typing import Generic, List, TypeVar\nfrom chili import init_dataclass\n\nT = TypeVar("T")\n\n@dataclass\nclass Pet:\n    name: str\n\n@dataclass\nclass Animal:\n    name: str\n\n@dataclass\nclass CustomList(Generic[T]):\n    list: List[T]\n\n\npet_list = init_dataclass(\n    {"list": [\n        {"name": "Boo"},\n        {"name": "Bobek"},\n    ]},\n    CustomList[Pet]\n)\n\nassert isinstance(pet_list, CustomList)\nfor pet in pet_list.list:\n    assert isinstance(pet, Pet)\n\n\nanimal_list = init_dataclass(\n    {"list": [\n        {"name": "Boo"},\n        {"name": "Bobek"},\n    ]},\n    CustomList[Animal]\n)\n\nassert isinstance(pet_list, CustomList)\nfor animal in animal_list.list:\n    assert isinstance(animal, Animal)\n```\n\nIn the above example there are three definitions of dataclasses: `Pet`, `Animal` and `CustomList`. \n`Pet` and `Animal` are just ordinary dataclasses but `CustomList` is a generic class, parametrised with `T` parameter. \nThis means we can have subtypes, like: `CustomList[Pet]`, `CustomList[Animal]` or even `CustomList[Dict]`.\n\n`init_dataclass` function understands that passed type is a generic type, and can handle it as suspected. \n\nHydration of dataclass inheriting from another generic dataclasses is also supported, \nonly if that dataclass specifies the parameters:\n\n```python\nfrom dataclasses import dataclass\nfrom typing import Generic, List, TypeVar\nfrom chili import init_dataclass\n\nT = TypeVar("T")\n\n@dataclass\nclass Pet:\n    name: str\n\n@dataclass\nclass Animal:\n    name: str\n\n@dataclass\nclass CustomList(Generic[T]):\n    list: List[T]\n\n@dataclass\nclass ExtendedGenericList(CustomList, Generic[T]):\n    ...\n\n@dataclass\nclass ExtendedList(CustomList[Pet]):\n    ...\n\n# this will work\npet_list = init_dataclass(\n    {"list": [\n        {"name": "Boo"},\n        {"name": "Bobek"},\n    ]},\n    ExtendedGenericList[Pet]\n)\n\n# this will fail\nfailed_pet_list = init_dataclass(\n    {"list": [\n        {"name": "Boo"},\n        {"name": "Bobek"},\n    ]},\n    ExtendedList\n)\n```\n\nIn the above example `ExtendedList` will fail during initialisation, the reason for that is information \nrequired to parametrise this class and probably its subclasses or any other classes aggregated by this class is lost. \nFor now this behaviour is not supported for auto-hydration mode. `ExtendedGenericList[Pet]` will work as expected.\n\n## Supported data types\n\n### `bool`\n\nPassed value is automatically hydrated to boolean with python\'s built-in `bool` on hydration and extraction.\n\n### `dict`\n\nPassed value is automatically hydrated to dict with python\'s built-in `dict` on hydration and extraction.\n\n### `float`\n\nPassed value is automatically hydrated to float with python\'s built-in `float` on hydration and extraction.\n\n### `frozenset`\n\nPassed value is automatically hydrated to frozen set with python\'s built-in `frozenset` and extracted to `list`.\n\n### `int`\n\nPassed value is automatically hydrated to int with python\'s built-in `int` on hydration and extraction.\n\n### `list`\n\nPassed value is automatically hydrated to list with python\'s built-in `list` on hydration and extraction.\n\n### `set`\n\nPassed value is automatically hydrated to set with python\'s built-in `set` and extracted to `list`.\n\n### `str`\n\nPassed value is automatically hydrated to string with python\'s built-in `str` on hydration and extraction.\n\n### `tuple`\n\nPassed value is automatically hydrated to tuple with python\'s built-in `tuple` and extracted to `list`.\n\n### `collections.namedtuple`\n\nPassed value is automatically hydrated to named tuple and extracted to `list`.\n\n### `collections.deque`\n\nPassed value is automatically hydrated to an instance of `collections.deque` and extracted to `list`.\n\n### `collections.OrderedDict`\n\nPassed value is automatically hydrated to an instance of `collections.OrderedDict` and extracted to `dict`.\n\n### `datetime.date`\n\nPassed value must be valid ISO-8601 date string, then it is automatically hydrated to an instance of `datetime.date` \nclass and extracted to ISO-8601 format compatible string.\n\n### `datetime.datetime`\n\nPassed value must be valid ISO-8601 date time string, then it is automatically hydrated to an instance of `datetime.datetime` \nclass and extracted to ISO-8601 format compatible string.\n\n### `datetime.time`\n\nPassed value must be valid ISO-8601 time string, then it is automatically hydrated to an instance of `datetime.time` \nclass and extracted to ISO-8601 format compatible string.\n\n### `datetime.timedelta`\n\nPassed value must be valid ISO-8601 duration string, then it is automatically hydrated to an instance of `datetime.timedelta`\nclass and extracted to ISO-8601 format compatible string.\n\n### `decimal.Decimal`\n\nPassed value must be a string containing valid decimal number representation, for more please read python\'s manual\nabout [`decimal.Decimal`](https://docs.python.org/3/library/decimal.html#decimal.Decimal), on extraction value is\nextracted back to string.\n\n### `enum.Enum`\n\nSupports hydration of all instances of `enum.Enum` subclasses as long as value can be assigned\nto one of the members defined in the specified `enum.Enum` subclass. During extraction the value is\nextracted to value of the enum member.\n\n### `enum.IntEnum`\n\nSame as `enum.Enum`.\n\n### `typing.Any`\n\nPassed value is unchanged during hydration and extraction process.\n\n### `typing.AnyStr`\n\nSame as `str`\n\n### `typing.Deque`\n\nSame as `collection.dequeue` with one exception, if subtype is defined, eg `typing.Deque[int]` each item inside queue\nis hydrated accordingly to subtype.\n\n### `typing.Dict`\n\nSame as `dict` with exception that keys and values are respectively hydrated and extracted to match\nannotated type.\n\n### `typing.FrozenSet`\n\nSame as `frozenset` with exception that values of a frozen set are respectively hydrated and extracted to\nmatch annotated type.\n\n### `typing.List`\n\nSame as `list` with exception that values of a list are respectively hydrated and extracted to match annotated type.\n\n### `typing.NamedTuple`\n\nSame as `namedtuple`.\n\n### `typing.Optional`\n\nOptional types can carry additional `None` value which chili\'s hydration process will respect, so for example \nif your type is `typing.Optional[int]` `None` value is not hydrated to `int`.\n\n### `typing.Set`\n\nSame as `set` with exception that values of a set are respectively hydrated and extracted to match annotated type.\n\n### `typing.Tuple`\n\nSame as `tuple` with exception that values of a set are respectively hydrated and extracted to match annotated types.\nEllipsis operator (`...`) is also supported.\n\n### `typing.TypedDict`\n\nSame as `dict` but values of a dict are respectively hydrated and extracted to match annotated types. \n\n\n### `typing.Generic\n\nOnly parametrised generic classes are supported, dataclasses that extends other Generic classes without parametrisation will fail.\n\n\n### `typing.Union`\n\nLimited support for Unions.\n\n## API\n\n### **`chili.hydrate`**(**`value`**: _`typing.Any`_, **`type_name`**: _`Type[T]`_, **`strict`**: _`bool`_ = `False`, **`mapping`**: _`chili.Mapper`_ = `None`) -> _`T`_\n\nHydrates given value into instance of passed type. If hydration fails, it returns passed value as a result, \nif strict mode is set to `True` it raises `InvalidValueError`.\n\n### **`chili.extract`**(**`value`**: _`typing.Any`_, **`strict`**: _`bool`_ = `False`, **`mapping`**: _`chili.Mapper`_ = `None`) -> _`typing.Any`_\n\nExtracts given value into primitive or set of primitives. If extraction fails, it returns passed value as a result, if\nstrict mode is set to `True` it raises `InvalidValueError`.\n\n### **`chili.init_dataclass`**(**`value`**: _`dict`_, **`type_name`**: _`Type[T]`_, **`mapping`**: _`chili.Mapper`_ = `None`) -> _`T`_\n\n`init_dataclass` function is instantiating dataclass of specified `type_name` and will hydrate the instance \nwith values passed in `value` dictionary. Each of the passed dictionary\'s keys must correspond to dataclass\'\nattributes in order to be properly interpreted. This rule can be broken if valid mapping is passed to the \nfunction.\n\nThis function support complex and nested hydration, which means if your dataclass aggregates other dataclasses \nor defines complex typing, `init_dataclass` function will respect your type annotations and will cast values \nto match the defined types. \n\nIf attributes in your dataclass do not specify the type value will be hydrated in to a newly created instance as is.\n### **`chili.asdict`**(**`value`**, **`mapping`**: _`chili.Mapper`_ = `None`) -> _`Dict[str, typing.Any]`_\n\n`asdict` is the opposite of `init_dataclass` function, it takes an instance of dataclass as argument, and\nextracts its members to a dictionary, so the returned data can be stored as json object or easily serialised \nto any other format. Additionally, `mapping` argument allows changing data representation on the fly.\n\n> Please note `Chili` is not a data validation library, although `Chili` performs some validation and casting\n> behind the scenes it does it only to ensure type consistency.\n\n',
    'author': 'Dawid Kraczkowski',
    'author_email': 'dawid.kraczkowski@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/kodemore/chili',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
