{"id":13501540,"url":"https://github.com/konradhalas/dacite","last_synced_at":"2025-05-14T22:06:06.979Z","repository":{"id":37292525,"uuid":"123735834","full_name":"konradhalas/dacite","owner":"konradhalas","description":"Simple creation of data classes from dictionaries.","archived":false,"fork":false,"pushed_at":"2025-03-17T15:25:37.000Z","size":528,"stargazers_count":1865,"open_issues_count":55,"forks_count":107,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-05-07T21:58:13.027Z","etag":null,"topics":["dataclasses"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/konradhalas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-03T22:12:48.000Z","updated_at":"2025-05-04T12:10:49.000Z","dependencies_parsed_at":"2024-04-11T21:48:54.680Z","dependency_job_id":"9d5328f8-d486-4162-a667-661fd6d71f97","html_url":"https://github.com/konradhalas/dacite","commit_stats":{"total_commits":182,"total_committers":11,"mean_commits":"16.545454545454547","dds":"0.10439560439560436","last_synced_commit":"02ee99348d4c8354fa309b8d1f3525dafda592e6"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fdacite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fdacite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fdacite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fdacite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/konradhalas","download_url":"https://codeload.github.com/konradhalas/dacite/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254235694,"owners_count":22036963,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dataclasses"],"created_at":"2024-07-31T22:01:41.012Z","updated_at":"2025-05-14T22:06:06.958Z","avatar_url":"https://github.com/konradhalas.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"![](https://user-images.githubusercontent.com/1078369/212840759-174c0f2b-d446-4c3a-b97c-67a0b912e7f6.png)\n\n# dacite\n\n[![Build Status](https://travis-ci.org/konradhalas/dacite.svg?branch=master)](https://travis-ci.org/konradhalas/dacite)\n[![Coverage Status](https://coveralls.io/repos/github/konradhalas/dacite/badge.svg?branch=master)](https://coveralls.io/github/konradhalas/dacite?branch=master)\n[![License](https://img.shields.io/pypi/l/dacite.svg)](https://pypi.python.org/pypi/dacite/)\n[![Version](https://img.shields.io/pypi/v/dacite.svg)](https://pypi.python.org/pypi/dacite/)\n[![Python versions](https://img.shields.io/pypi/pyversions/dacite.svg)](https://pypi.python.org/pypi/dacite/)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)\n\nThis module simplifies creation of data classes ([PEP 557][pep-557])\nfrom dictionaries.\n\n## Installation\n\nTo install dacite, simply use `pip`:\n\n```\n$ pip install dacite\n```\n\n## Requirements\n\nMinimum Python version supported by `dacite` is 3.7.\n\n## Quick start\n\n```python\nfrom dataclasses import dataclass\nfrom dacite import from_dict\n\n\n@dataclass\nclass User:\n    name: str\n    age: int\n    is_active: bool\n\n\ndata = {\n    'name': 'John',\n    'age': 30,\n    'is_active': True,\n}\n\nuser = from_dict(data_class=User, data=data)\n\nassert user == User(name='John', age=30, is_active=True)\n```\n\n## Features\n\nDacite supports following features:\n\n- nested structures\n- (basic) type checking\n- optional fields (i.e. `typing.Optional`)\n- unions\n- generics\n- forward references\n- collections\n- custom type hooks\n- case conversion\n\n## Motivation\n\nPassing plain dictionaries as a data container between your functions or\nmethods isn't a good practice. Of course you can always create your\ncustom class instead, but this solution is an overkill if you only want\nto merge a few fields within a single object.\n\nFortunately Python has a good solution to this problem - data classes.\nThanks to `@dataclass` decorator you can easily create a new custom\ntype with a list of given fields in a declarative manner. Data classes\nsupport type hints by design.\n\nHowever, even if you are using data classes, you have to create their\ninstances somehow. In many such cases, your input is a dictionary - it\ncan be a payload from a HTTP request or a raw data from a database. If\nyou want to convert those dictionaries into data classes, `dacite` is\nyour best friend.\n\nThis library was originally created to simplify creation of type hinted\ndata transfer objects (DTO) which can cross the boundaries in the\napplication architecture.\n\nIt's important to mention that `dacite` is not a data validation library.\nThere are dozens of awesome data validation projects and it doesn't make\nsense to duplicate this functionality within `dacite`. If you want to \nvalidate your data first, you should combine `dacite` with one of data \nvalidation library.\n\nPlease check [Use Case](#use-case) section for a real-life example.\n\n## Usage\n\nDacite is based on a single function - `dacite.from_dict`. This function\ntakes 3 parameters:\n\n- `data_class` - data class type\n- `data` - dictionary of input data\n- `config` (optional) - configuration of the creation process, instance\nof `dacite.Config` class\n\nConfiguration is a (data) class with following fields:\n\n- `type_hooks`\n- `cast`\n- `forward_references`\n- `check_types`\n- `strict`\n- `strict_unions_match`\n- `convert_key`\n\nThe examples below show all features of `from_dict` function and usage\nof all `Config` parameters.\n\n### Nested structures\n\nYou can pass a data with nested dictionaries and it will create a proper\nresult.\n\n```python\n@dataclass\nclass A:\n    x: str\n    y: int\n\n\n@dataclass\nclass B:\n    a: A\n\n\ndata = {\n    'a': {\n        'x': 'test',\n        'y': 1,\n    }\n}\n\nresult = from_dict(data_class=B, data=data)\n\nassert result == B(a=A(x='test', y=1))\n```\n\n### Optional fields\n\nWhenever your data class has a `Optional` field and you will not provide\ninput data for this field, it will take the `None` value.\n\n```python\nfrom typing import Optional\n\n@dataclass\nclass A:\n    x: str\n    y: Optional[int]\n\n\ndata = {\n    'x': 'test',\n}\n\nresult = from_dict(data_class=A, data=data)\n\nassert result == A(x='test', y=None)\n```\n\n### Unions\n\nIf your field can accept multiple types, you should use `Union`. Dacite\nwill try to match data with provided types one by one. If none will\nmatch, it will raise `UnionMatchError` exception.\n\n```python\nfrom typing import Union\n\n@dataclass\nclass A:\n    x: str\n\n@dataclass\nclass B:\n    y: int\n\n@dataclass\nclass C:\n    u: Union[A, B]\n\n\ndata = {\n    'u': {\n        'y': 1,\n    },\n}\n\nresult = from_dict(data_class=C, data=data)\n\nassert result == C(u=B(y=1))\n```\n\n### Collections\n\nDacite supports fields defined as collections. It works for both - basic\ntypes and data classes.\n\n```python\n@dataclass\nclass A:\n    x: str\n    y: int\n\n\n@dataclass\nclass B:\n    a_list: List[A]\n\n\ndata = {\n    'a_list': [\n        {\n            'x': 'test1',\n            'y': 1,\n        },\n        {\n            'x': 'test2',\n            'y': 2,\n        }\n    ],\n}\n\nresult = from_dict(data_class=B, data=data)\n\nassert result == B(a_list=[A(x='test1', y=1), A(x='test2', y=2)])\n```\n\n### Generics\n\nDacite supports generics: (multi-)generic dataclasses, but also dataclasses that inherit from a generic dataclass, or dataclasses that have a generic dataclass field.\n\n```python\nT = TypeVar('T')\nU = TypeVar('U')\n\n@dataclass\nclass X:\n    a: str\n\n\n@dataclass\nclass A(Generic[T, U]):\n    x: T\n    y: list[U]\n\ndata = {\n    'x': {\n        'a': 'foo',\n    },\n    'y': [1, 2, 3]\n}\n\nresult = from_dict(data_class=A[X, int], data=data)\n\nassert result == A(x=X(a='foo'), y=[1,2,3])\n\n\n@dataclass\nclass B(A[X, int]):\n    z: str\n\ndata = {\n    'x': {\n        'a': 'foo',\n    },\n    'y': [1, 2, 3],\n    'z': 'bar'\n}\n\nresult = from_dict(data_class=B, data=data)\n\nassert result == B(x=X(a='foo'), y=[1,2,3], z='bar')\n\n\n@dataclass\nclass C:\n    z: A[X, int]\n\ndata = {\n    'z': {\n        'x': {\n            'a': 'foo',\n        },\n        'y': [1, 2, 3],\n    }\n}\n\nresult = from_dict(data_class=C, data=data)\n\nassert result == C(z=A(x=X(a='foo'), y=[1,2,3]))\n```\n\n### Type hooks\n\nYou can use `Config.type_hooks` argument if you want to transform the input \ndata of a data class field with given type into the new value. You have to \npass a following mapping: `{Type: callable}`, where `callable` is a \n`Callable[[Any], Any]`.\n\n```python\n@dataclass\nclass A:\n    x: str\n\n\ndata = {\n    'x': 'TEST',\n}\n\nresult = from_dict(data_class=A, data=data, config=Config(type_hooks={str: str.lower}))\n\nassert result == A(x='test')\n```\n\nIf a data class field type is a `Optional[T]` you can pass both - \n`Optional[T]` or just `T` - as a key in `type_hooks`. The same with generic \ncollections, e.g. when a field has type `List[T]` you can use `List[T]` to \ntransform whole collection or `T` to transform each item. \n\n### Casting\n\nIt's a very common case that you want to create an instance of a field type \nfrom the input data with just calling your type with the input value. Of \ncourse you can use `type_hooks={T: T}` to achieve this goal but `cast=[T]` is \nan easier and more expressive way. It also works with base classes - if `T` \nis a base class of type `S`, all fields of type `S` will be also \"casted\".\n\n```python\nfrom enum import Enum\n\nclass E(Enum):\n    X = 'x'\n    Y = 'y'\n    Z = 'z'\n\n@dataclass\nclass A:\n    e: E\n\n\ndata = {\n    'e': 'x',\n}\n\nresult = from_dict(data_class=A, data=data, config=Config(cast=[E]))\n\n# or\n\nresult = from_dict(data_class=A, data=data, config=Config(cast=[Enum]))\n\nassert result == A(e=E.X)\n```\n\n### Forward References\n\nDefinition of forward references can be passed as a `{'name': Type}` mapping to \n`Config.forward_references`. This dict is passed to `typing.get_type_hints()` as the \n`globalns` param when evaluating each field's type.\n\n```python\n@dataclass\nclass X:\n    y: \"Y\"\n\n@dataclass\nclass Y:\n    s: str\n\ndata = from_dict(X, {\"y\": {\"s\": \"text\"}}, Config(forward_references={\"Y\": Y}))\nassert data == X(Y(\"text\"))\n```\n\n### Type checking\n\nIf you want to trade-off type checking for speed, you can disabled type checking by setting `check_types` to `False`.\n\n```python\n@dataclass\nclass A:\n    x: str\n\n# won't throw an error even though the type is wrong\nfrom_dict(A, {'x': 4}, config=Config(check_types=False)) \n```\n\n### Strict mode\n\nBy default `from_dict` ignores additional keys (not matching data class field) \nin the input data. If you want change this behaviour set `Config.strict` to \n`True`. In case of unexpected key `from_dict` will raise `UnexpectedDataError` \nexception.\n\n### Strict unions match\n\n`Union` allows to define multiple possible types for a given field. By default \n`dacite` is trying to find the first matching type for a provided data and it \nreturns instance of this type. It means that it's possible that there are other \nmatching types further on the `Union` types list. With `strict_unions_match` \nonly a single match is allowed, otherwise `dacite` raises `StrictUnionMatchError`.\n\n## Convert key\n\nYou can pass a callable to the `convert_key` configuration parameter to convert camelCase to snake_case.\n\n```python\ndef to_camel_case(key: str) -\u003e str:\n    first_part, *remaining_parts = key.split('_')\n    return first_part + ''.join(part.title() for part in remaining_parts)\n\n@dataclass\nclass Person:\n    first_name: str\n    last_name: str\n\ndata = {\n    'firstName': 'John',\n    'lastName': 'Doe'\n}\n\nresult = from_dict(Person, data, Config(convert_key=to_camel_case))\n\nassert result == Person(first_name='John', last_name='Doe') \n```\n\n## Exceptions\n\nWhenever something goes wrong, `from_dict` will raise adequate\nexception. There are a few of them:\n\n- `WrongTypeError` - raised when a type of a input value does not match\nwith a type of a data class field\n- `MissingValueError` - raised when you don't provide a value for a\nrequired field\n- `UnionMatchError` - raised when provided data does not match any type\nof `Union`\n- `ForwardReferenceError` - raised when undefined forward reference encountered in\ndataclass\n- `UnexpectedDataError` - raised when `strict` mode is enabled and the input \ndata has not matching keys\n- `StrictUnionMatchError` - raised when `strict_unions_match` mode is enabled \nand the input data has ambiguous `Union` match\n\n## Development\n\nFirst of all - if you want to submit your pull request, thank you very much! \nI really appreciate your support.\n\nPlease remember that every new feature, bug fix or improvement should be tested. \n100% code coverage is a must-have. \n\nWe are using a few static code analysis tools to increase the code quality \n(`black`, `mypy`, `pylint`). Please make sure that you are not generating any \nerrors/warnings before you submit your PR. You can find current configuration\nin `.github/*` directory.\n\nLast but not least, if you want to introduce new feature, please discuss it \nfirst within an issue.\n\n### How to start\n\nClone `dacite` repository:\n\n```bash\ngit clone git@github.com:konradhalas/dacite.git\n```\n\nCreate and activate virtualenv in the way you like:\n\n```bash\npython3 -m venv dacite-env\nsource dacite-env/bin/activate\n```\n\nInstall all `dacite` dependencies:\n\n```bash\npip install -e .[dev]\n```\n\nAnd, optionally but recommended, install pre-commit hook for black:\n\n```bash\npre-commit install\n```\n\nTo run tests you just have to fire:\n\n```bash\npytest\n```\n\n### Performance testing\n\n`dacite` is a small library, but its use is potentially very extensive. Thus, it is crucial\nto ensure good performance of the library.\n\nWe achieve that with the help of `pytest-benchmark` library, and a suite of dedicated performance tests\nwhich can be found in the `tests/performance` directory. The CI process runs these tests automatically,\nbut they can also be helpful locally, while developing the library.\n\nWhenever you run `pytest` command, a new benchmark report is saved to `/.benchmarks` directory.\nYou can easily compare these reports by running: `pytest-benchmark compare`, which will load all the runs\nand display them in a table, where you can compare the performance of each run.\n\nYou can even specify which particular runs you want to compare, e.g. `pytest-benchmark compare 0001 0003 0005`.\n \n## Use case\n\nThere are many cases when we receive \"raw\" data (Python dicts) as a input to \nour system. HTTP request payload is a very common use case. In most web \nframeworks we receive request data as a simple dictionary. Instead of \npassing this dict down to your \"business\" code, it's a good idea to create \nsomething more \"robust\".\n\nFollowing example is a simple `flask` app - it has single `/products` endpoint.\nYou can use this endpoint to \"create\" product in your system. Our core \n`create_product` function expects data class as a parameter. Thanks to `dacite` \nwe can easily build such data class from `POST` request payload.\n\n\n```python\nfrom dataclasses import dataclass\nfrom typing import List\n\nfrom flask import Flask, request, Response\n\nimport dacite\n\napp = Flask(__name__)\n\n\n@dataclass\nclass ProductVariantData:\n    code: str\n    description: str = ''\n    stock: int = 0\n\n\n@dataclass\nclass ProductData:\n    name: str\n    price: float\n    variants: List[ProductVariantData]\n\n\ndef create_product(product_data: ProductData) -\u003e None:\n    pass  # your business logic here\n\n\n@app.route(\"/products\", methods=['POST'])\ndef products():\n    product_data = dacite.from_dict(\n        data_class=ProductData,\n        data=request.get_json(),\n    )\n    create_product(product_data=product_data)\n    return Response(status=201)\n\n```\n\nWhat if we want to validate our data (e.g. check if `code` has 6 characters)? \nSuch features are out of scope of `dacite` but we can easily combine it with \none of data validation library. Let's try with \n[marshmallow](https://marshmallow.readthedocs.io).\n\nFirst of all we have to define our data validation schemas:\n\n```python\nfrom marshmallow import Schema, fields, ValidationError\n\n\ndef validate_code(code):\n    if len(code) != 6:\n        raise ValidationError('Code must have 6 characters.')\n\n\nclass ProductVariantDataSchema(Schema):\n    code = fields.Str(required=True, validate=validate_code)\n    description = fields.Str(required=False)\n    stock = fields.Int(required=False)\n\n\nclass ProductDataSchema(Schema):\n    name = fields.Str(required=True)\n    price = fields.Decimal(required=True)\n    variants = fields.Nested(ProductVariantDataSchema(many=True))\n```\n\nAnd use them within our endpoint:\n\n```python\n@app.route(\"/products\", methods=['POST'])\ndef products():\n    schema = ProductDataSchema()\n    result, errors = schema.load(request.get_json())\n    if errors:\n        return Response(\n            response=json.dumps(errors), \n            status=400, \n            mimetype='application/json',\n        )\n    product_data = dacite.from_dict(\n        data_class=ProductData,\n        data=result,\n    )\n    create_product(product_data=product_data)\n    return Response(status=201)\n```\n\nStill `dacite` helps us to create data class from \"raw\" dict with validated data.\n\n### Cache\n\n`dacite` uses some LRU caching to improve its performance where possible. To use the caching utility:\n\n```python\nfrom dacite import set_cache_size, get_cache_size, clear_cache\n\nget_cache_size()  # outputs the current LRU max_size, default is 2048\nset_cache_size(4096)  # set LRU max_size to 4096\nset_cache_size(None)  # set LRU max_size to None\nclear_cache()  # Clear the cache\n```\n\nThe caching is completely transparent from the interface perspective.\n\n## Changelog\n\nFollow `dacite` updates in [CHANGELOG][changelog].\n\n## Authors\n\nCreated by [Konrad Hałas][halas-homepage].\n\n[pep-557]: https://www.python.org/dev/peps/pep-0557/\n[halas-homepage]: https://konradhalas.pl\n[changelog]: https://github.com/konradhalas/dacite/blob/master/CHANGELOG.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkonradhalas%2Fdacite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkonradhalas%2Fdacite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkonradhalas%2Fdacite/lists"}