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

packages = \
['serdelicacy']

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

setup_kwargs = {
    'name': 'serdelicacy',
    'version': '0.12.0',
    'description': 'Serialize/deserialize Python objects from/to typed structures.',
    'long_description': '# serdelicacy\n\n[![image-version](https://img.shields.io/pypi/v/serdelicacy.svg)](https://python.org/pypi/serdelicacy)\n[![image-license](https://img.shields.io/pypi/l/serdelicacy.svg)](https://python.org/pypi/serdelicacy)\n[![image](https://img.shields.io/pypi/pyversions/serdelicacy.svg)](https://python.org/pypi/serdelicacy)\n[![image-ci](https://github.com/pappasam/serdelicacy/workflows/serdelicacy%20ci/badge.svg)](https://github.com/pappasam/serdelicacy/actions?query=workflow%3A%22serdelicacy+ci%22)\n\nSerialize (`serdelicacy.dump`) and deserialize (`serdelicacy.load`) from/to strongly-typed, native Python data structures.\n\n## Motivation\n\nNo typing-focused [serde](https://en.wikipedia.org/wiki/Serialization) library in Python satisfies me. Call me needy, but when I must translate loosely-typed data structures (like `list` and `dict`) to strongly-typed data structures (like `NamedTuple` and `dataclasses.dataclass`), I want the following capabilities:\n\n1. Effortless deserialization of unstructured Python types into structured, type-hinted Python types (`dataclasses.dataclass`, `typing.NamedTuple`)\n2. Effortless serialization of structured, type-hinted Python objects into unstructured Python types (eg, the reverse)\n3. Clear error messages when serde fails at runtime\n4. No inherited, non-standard types. dataclasses, NamedTuples, and other standard Python types are bread and butter\n5. Editor support: I like my autocompletion, so I jump through lots of hoops to make this library compatible with Jedi\n6. Handle [optional properties](https://www.typescriptlang.org/docs/handbook/interfaces.html#optional-properties) with a domain-specific `serdelicacy.OptionalProperty`\n7. Provide option to automatically convert primitive types, but avoid converting ambiguous types (`Union`, `TypeVar`, etc). Handle `Optional` and `serdelicacy.OptionalProperty`\n8. Require no 3rd party dependencies; Python 3.8+\n\n## Installation\n\n```bash\n# With pip\npip install serdelicacy\n\n# With poetry\npoetry add serdelicacy\n```\n\n## Usage\n\nSee [examples folder](https://github.com/pappasam/serdelicacy/tree/master/example) if you\'d like to get your hands dirty. Otherwise, keep reading for a complete, real-world example.\n\n### Example: Libraries and Books\n\nAssume that you receive a `JSON` list of libraries containing each library\'s name and a list of each library\'s books.\n\n```json\n[\n  {\n    "name": "Clark County Library",\n    "books": [\n      {\n        "title": "Hello, World!",\n        "author": "Susy Smith",\n        "year": 1929,\n        "tags": ["boring"]\n      },\n      {\n        "title": "The great showman",\n        "author": "Beth John"\n      },\n      {\n        "title": "My favorite pony",\n        "author": null\n      }\n    ]\n  },\n  {\n    "name": "Only 1 book here",\n    "books": [\n      {\n        "title": "The great fun time",\n        "author": "Smitty",\n        "year": 1950,\n        "tags": ["swell"]\n      }\n    ]\n  }\n]\n```\n\nNow you want to ingest this document into Python. Your first step is probably to deserialize the JSON string (or file) into Python data structures.\n\n```python\nimport json\nfrom pprint import pprint\n\nwith open("libraries.json", "r") as infile:\n    libraries_raw = json.load(infile)\n\npprint(libraries_raw)\nprint(type(libraries_raw))\nprint(type(libraries_raw[0]))\n```\n\nAssuming the JSON is read from a file called `libraries.py`, the preceding script will print:\n\n```text\n[{\'books\': [{\'author\': \'Susy Smith\',\n             \'tags\': [\'boring\'],\n             \'title\': \'Hello, World!\',\n             \'year\': 1929},\n            {\'author\': \'Beth John\', \'title\': \'The great showman\'},\n            {\'author\': None, \'title\': \'My favorite pony\'}],\n  \'name\': \'Clark County Library\'},\n {\'books\': [{\'author\': \'Smitty\',\n             \'tags\': [\'swell\'],\n             \'title\': \'The great fun time\',\n             \'year\': 1950}],\n  \'name\': \'Only 1 book here\'}]\n<class \'list\'>\n<class \'dict\'>\n```\n\nSome observations:\n\n1. Python\'s native `json` module deserializes the JSON string / document into Python\'s primitive (or primitive-like) types\n2. `null` is translated to Python\'s `None`\n3. The first list element is a dictionary. So Python appears to have translated the JSON into a list of dictionaries.\n4. There is little inherent structure to the Python objects deserialized by the JSON module. By this, I mean that we have no way of knowing whether the dictionaries contain keys that we expect or are structured improperly. Should books also have an `"isbn"` field? Does code we write that uses `libraries_raw` expect an `"isbn"` field? What happens if there are missing tags?\n\nThe first 3 items are merely facts; `serdelicacy` accepts these facts and builds on them. The 4th item in this list is THE problem that `serdelicacy` is designed to solve. If we take the above Python dictionary and associate it with a Python variable named `LIBRARIES`, we can define a strongly-typed Python container that `serdelicacy` can use to ingest `LIBRARIES`.\n\n```python\nfrom dataclasses import dataclass, field\nfrom pprint import pprint\nfrom typing import List, Optional\n\nimport serdelicacy\nfrom serdelicacy import OptionalProperty\n\n[\n    {\n        "books": [\n            {\n                "author": "Susy Smith",\n                "tags": ["boring"],\n                "title": "Hello, World!",\n                "year": 1929,\n            },\n            {"author": "Beth John", "title": "The great showman"},\n            {"author": None, "title": "My favorite pony"},\n        ],\n        "name": "Clark County Library",\n    },\n    {\n        "books": [\n            {\n                "author": "Smitty",\n                "tags": ["swell"],\n                "title": "The great fun time",\n                "year": 1950,\n            }\n        ],\n        "name": "Only 1 book here",\n    },\n]\n\n@dataclass\nclass Book:\n    author: Optional[str]\n    title: str\n    year: OptionalProperty[int]\n    tags: List[str] = field(default_factory=list)\n\n@dataclass\nclass Library:\n    books: List[Book]\n    name: str\n\nLIBRARIES_LOADED = serdelicacy.load(LIBRARIES, List[Library])\nprint(LIBRARIES_LOADED[0].name)\nprint(LIBRARIES_LOADED[0].books[1].author)\npprint(serdelicacy.dump(LIBRARIES_LOADED))\n```\n\nRunning the above script, we get the following output to the terminal:\n\n```text\n[{\'books\': [{\'author\': \'Susy Smith\',\n             \'tags\': [\'boring\'],\n             \'title\': \'Hello, World!\',\n             \'year\': 1929},\n            {\'author\': \'Beth John\', \'tags\': [], \'title\': \'The great showman\'},\n            {\'author\': None, \'tags\': [], \'title\': \'My favorite pony\'}],\n  \'name\': \'Clark County Library\'},\n {\'books\': [{\'author\': \'Smitty\',\n             \'tags\': [\'swell\'],\n             \'title\': \'The great fun time\',\n             \'year\': 1950}],\n  \'name\': \'Only 1 book here\'}]\n```\n\nNotice how we have the following features:\n\n1. Data structures are loaded, recursively, without you needing to write anything more than a couple standard Python classes.\n2. For missing properties / dictionary keys (for example, `Book.tags`), we can set a default value in our dataclass using standard Python and `serdelicacy` adds the default value to our structure\n3. For missing properties without default values, serdelicacy intelligently omits them when re-serializing the result. There is also an option to `serdelicacy.load` that allows you to convert missing values to `None` and keep the keys in the output. For all other desired default values, just use `dataclasses.field`; no need to re-invent the wheel!\n\nWhat about additional validation, you may ask? Again, just use dataclasses! Assume that, for some reason, no book can possibly be published before 1930, and that a book published before 1930 invalidates all data. No problem, just use the standard method `__post_init__` on the relevant dataclass!\n\n```python\nfrom dataclasses import dataclass, field\nfrom pprint import pprint\nfrom typing import List, Optional\n\nimport serdelicacy\nfrom serdelicacy import OptionalProperty\n\nLIBRARIES = [\n    {\n        "books": [\n            {\n                "author": "Susy Smith",\n                "tags": ["boring"],\n                "title": "Hello, World!",\n                "year": 1929,\n            },\n            {"author": "Beth John", "title": "The great showman"},\n            {"author": None, "title": "My favorite pony"},\n        ],\n        "name": "Clark County Library",\n    },\n    {\n        "books": [\n            {\n                "author": "Smitty",\n                "tags": ["swell"],\n                "title": "The great fun time",\n                "year": 1950,\n            }\n        ],\n        "name": "Only 1 book here",\n    },\n]\n\n@dataclass\nclass Book:\n    author: Optional[str]\n    title: str\n    year: OptionalProperty[int]\n    tags: List[str] = field(default_factory=list)\n\n    def __post_init__(self) -> None:\n        if self.year and self.year < 1930:\n            raise ValueError(\n                f"Received illegal year {self.year}, cannot be before 1930"\n            )\n\n@dataclass\nclass Library:\n    books: List[Book]\n    name: str\n\nLIBRARIES_LOADED = serdelicacy.load(LIBRARIES, List[Library])\n```\n\nRunning this script should give you a clear error message containing a description of the error you received, along with each intermediate object in the recursive chain to help you debug further. This structure makes it incredibly easy to see not only what your error is, but where it occurred in both the data `serdelicacy.load` receives but also in the types `serdelicacy.load` uses to attempt to deserialize the received data.\n\nIn serde, when working with resources external to your system, errors are inevitable. These error messages should hopefully make debugging your errors less annoying.\n\n## Frequent issues\n\n### My JSON keys contain whitespace, etc\n\nSimple solution: use `typeing.TypeDict`\'s [backwards-compatibility syntax](https://www.python.org/dev/peps/pep-0589/#alternative-syntax).\n\n```python\nfrom pprint import pprint\nfrom typing import List, TypedDict\n\nimport serdelicacy\nfrom serdelicacy import OptionalProperty\n\nDATA = [\n    {\n        "weird, key": 1,\n        "normal": 2,\n    },\n    {\n        "normal": 3,\n    },\n]\n\nDataItem = TypedDict(\n    "DataItem",\n    {\n        "weird, key": OptionalProperty[int],\n        "normal": int,\n    },\n)\n\nLOADED = serdelicacy.load(DATA, List[DataItem])\n\nprint("Loaded data:")\npprint(LOADED)\n\nprint("Re-serialized data:")\npprint(serdelicacy.dump(LOADED))\n```\n\nThis prints the following to the console.\n\n```text\nLoaded data:\n[{\'normal\': 2, \'weird, key\': 1},\n {\'normal\': 3, \'weird, key\': <Undefined property>}]\nRe-serialized data:\n[{\'normal\': 2, \'weird, key\': 1}, {\'normal\': 3}]\n```\n\nTry changing values in your JSON data; you\'ll get runtime errors if your data does not conform to the above schema. Additionally, `mypy` should call out any misused variable keys / types. In short, this has enabled a type-safe load and a perfectly sane dump.\n\n## Local Development\n\nLocal development for this project is simple.\n\n**Dependencies**\n\nInstall the following tools manually.\n\n- [Poetry](https://github.com/sdispater/poetry#installation)\n- [GNU Make](https://www.gnu.org/software/make/)\n\n_Recommended_\n\n- [asdf](https://github.com/asdf-vm/asdf)\n\n**Set up development environment**\n\n```bash\nmake setup\n```\n\n**Run Tests**\n\n```bash\nmake test\n```\n\n## Notes\n\n- Initially inspired by [undictify](https://github.com/Dobiasd/undictify) and a PR I helped with. serdelicacy\'s goals are different; it\'s focused on serde instead of general function signature overrides.\n- I also notice some striking similarities with a library called [typedload](https://github.com/ltworf/typedload) (great minds think alike, I guess :p). I renamed my top-level functions to "load" and "dump" in typedload\'s homage. Unfortunately, as of version `1.20`, typedload does not handle all types of dataclasses elegantly (mainly, InitVar). Since typedload supports Python 3.5+, it never will elegantly handle all dataclasses without lots of unfortunate conditionals in the codebase. If you must use Python 3.7-, I suggest looking into typedload.\n\n## Written by\n\nSamuel Roeca *samuel.roeca@gmail.com*\n',
    'author': 'Sam Roeca',
    'author_email': 'samuel.roeca@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/pappasam/serdelicacy',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
