pax_global_header00006660000000000000000000000064137611264430014521gustar00rootroot0000000000000052 comment=d2206b2e4711859da0ea5862c395940f33693e80 dacite-1.6.0/000077500000000000000000000000001376112644300127565ustar00rootroot00000000000000dacite-1.6.0/.gitignore000066400000000000000000000000551376112644300147460ustar00rootroot00000000000000.pytest_cache/ *.pyc dist/ *.egg-info/ build/dacite-1.6.0/.pylintrc000066400000000000000000000427431376112644300146350ustar00rootroot00000000000000[MASTER] # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. jobs=1 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or # complex, nested conditions. limit-inference-results=100 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes # Specify a configuration file. #rcfile= # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once). You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=print-statement, parameter-unpacking, unpacking-in-except, old-raise-syntax, backtick, long-suffix, old-ne-operator, old-octal-literal, import-star-module-level, non-ascii-bytes-literal, raw-checker-failed, bad-inline-option, locally-disabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, use-symbolic-message-instead, apply-builtin, basestring-builtin, buffer-builtin, cmp-builtin, coerce-builtin, execfile-builtin, file-builtin, long-builtin, raw_input-builtin, reduce-builtin, standarderror-builtin, unicode-builtin, xrange-builtin, coerce-method, delslice-method, getslice-method, setslice-method, no-absolute-import, old-division, dict-iter-method, dict-view-method, next-method-called, metaclass-assignment, indexing-exception, raising-string, reload-builtin, oct-method, hex-method, nonzero-method, cmp-method, input-builtin, round-builtin, intern-builtin, unichr-builtin, map-builtin-not-iterating, zip-builtin-not-iterating, range-builtin-not-iterating, filter-builtin-not-iterating, using-cmp-argument, eq-without-hash, div-method, idiv-method, rdiv-method, exception-message-attribute, invalid-str-codec, sys-max-int, bad-python3-import, deprecated-string-function, deprecated-str-translate-call, deprecated-itertools-function, deprecated-types-field, next-method-defined, dict-items-not-iterating, dict-keys-not-iterating, dict-values-not-iterating, deprecated-operator-function, deprecated-urllib-function, xreadlines-attribute, deprecated-sys-function, exception-escape, comprehension-escape, missing-docstring, no-self-use, unidiomatic-typecheck, no-else-return, too-many-return-statements, too-many-branches, protected-access, bad-continuation, import-outside-toplevel, isinstance-second-argument-not-valid-type, unsubscriptable-object, comparison-with-callable, raise-missing-from # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable=c-extension-no-member [REPORTS] # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details. #msg-template= # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. output-format=text # Tells whether to display a full report or only the messages. reports=no # Activate the evaluation score. score=yes [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=sys.exit [LOGGING] # Format style used to check logging format string. `old` means using % # formatting, while `new` is for `{}` formatting. logging-format-style=old # Logging modules to check that the string format arguments are in logging # function parameter format. logging-modules=logging [SPELLING] # Limits count of emitted suggestions for spelling mistakes. max-spelling-suggestions=4 # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package.. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME, XXX, TODO [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # Tells whether to warn about missing members when the owner of the attribute # is inferred to be None. ignore-none=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid defining new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb # A regular expression matching the name of dummy variables (i.e. expected to # not be used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. Default to name # with leading underscore. ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=120 # Maximum number of lines in a module. max-module-lines=1000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma, dict-separator # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [SIMILARITIES] # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no # Minimum lines number of a similarity. min-similarity-lines=4 [BASIC] # Naming style matching correct argument names. argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- # naming-style. #argument-rgx= # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- # style. #attr-rgx= # Bad variable names which should always be refused, separated by a comma. bad-names=foo, bar, baz, toto, tutu, tata # Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- # attribute-naming-style. #class-attribute-rgx= # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names. Overrides class-naming- # style. #class-rgx= # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- # style. #const-rgx= # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # Naming style matching correct function names. function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- # naming-style. #function-rgx= # Good variable names which should always be accepted, separated by a comma. good-names=i, j, k, ex, Run, _, T # Include a hint for the correct naming format with invalid-name. include-naming-hint=no # Naming style matching correct inline iteration names. inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides # inlinevar-naming-style. #inlinevar-rgx= # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- # style. #method-rgx= # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- # style. #module-rgx= # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. # These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- # naming-style. #variable-rgx= [STRING] # This flag controls whether the implicit-str-concat-in-sequence should # generate a warning on implicit string concatenation in sequences defined over # several lines. check-str-concat-over-line-jumps=no [IMPORTS] # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma. deprecated-modules=optparse,tkinter.tix # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled). ext-import-graph= # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled). import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled). int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict, _fields, _replace, _source, _make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method. max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Maximum number of boolean expressions in an if statement. max-bool-expr=5 # Maximum number of branch for function / method body. max-branches=12 # Maximum number of locals for function / method body. max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of return / yield for function / method body. max-returns=6 # Maximum number of statements in function / method body. max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "BaseException, Exception". overgeneral-exceptions=BaseException, Exception dacite-1.6.0/.travis.yml000066400000000000000000000022051376112644300150660ustar00rootroot00000000000000dist: xenial language: python python: - 3.6 - 3.7 - 3.8 - 3.9-dev install: - pip install -e .[dev] script: - pytest --cov=dacite - black --check . - mypy dacite - pylint dacite after_success: coveralls stages: - test - deploy jobs: include: - stage: deploy script: skip python: 3.8 deploy: provider: pypi user: khalas password: secure: AsRZXKbagKofHu1JhqgKziomF/eIQZsGgp0scdAZxd1XwsDOwGvvzEJdgMmzxo3Wgvwa5rHBmZAjN8qYDyHLESCPUlVaE6bFztM5q2icZ7z6ReaY9807C5wb9xbDVwQ74GBLP3x1CAR0o2KcO2FS4Xc9TIGmHTY0yyrYzsose0Qn6hAEOpNcoAPR6eRUkIakyqVwj8JAdFBsB1EuqFIpgEwjmgQrv3ZUwTe+a7ERJE6Su3Q5qKU1tJFEbzMvhdBpTOfUujUdtNVx6A8FSivCBcGE1FgRz+ZEFcLxsnYNyw+cNgWjD7thyAE6RrSzNSn3ddMGwqYcXQkfOLICbdazDrcAAAhl+DJGPDilkbAo9cX8zoKY4v12bKAs5PFh6L/qArFAT6X7rMBDkqZ/i8O7Pxbvsm1G1yfupUzwBn+qlc4WxhrroqFa2/kLbPADrihiercyueoXU66+CzKHzmfvDhv7Yoz9bE1ZzgW0go7VsJXy93ZAuveB8i9+OMUsRBY8dTrIdBhd8IDpBet667ugStv+0yg2rhEQxw8q7mhLvwk2AAt2qX6dekhyrqKGrBVgxfWWqX0iyf/whIcUuKHZ6VBKFgwZ8DhCfvNw5FaRrKEbRc5TPhlNk0TyNNQh0rsSi7F+6hCwLcLl59MgWXoJkbpoK5yJZqA2YcSYXQAixFY= on: tags: true distributions: "sdist bdist_wheel" dacite-1.6.0/CHANGELOG.md000066400000000000000000000036601376112644300145740ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.6.0] - 2020-11-30 ### Added - Support `Type` fields ### Fixed - Handle generic collections with implicit `Any` inner types in Python 3.9 - Fix `InitVar` with inner data classes - Improve support for fixed length and undefined length tuples ## [1.5.1] - 2020-07-03 ### Added - Python 3.9 support ## [1.5.0] - 2020-05-02 ### Added - Add `strict_unions_match` config parameter - Support `Tuple` ### Fixed - The order of run type hooks in `Union` ## [1.4.0] - 2020-04-10 ### Added - Support `InitVar` ### Fixed - Fix `Union` type hooks ## [1.3.0] - 2020-03-14 ### Added - Support `Literal` - Support [PEP 561](https://www.python.org/dev/peps/pep-0561/) ## [1.2.1] - 2020-03-02 ### Fixed - Fix problem with a "numeric tower" and optional/new type ## [1.2.0] - 2020-01-05 ### Added - Support base classes in `cast` - Support collections in `cast` ### Changed - Handle "numeric tower" as described in [PEP 484](https://www.python.org/dev/peps/pep-0484/#the-numeric-tower) ## [1.1.0] - 2019-11-27 ### Added - Python 3.8 support - `cast` config parameter ### Changed - Validate type for generic collection fields [1.6.0]: https://github.com/konradhalas/dacite/compare/v1.5.1...v1.6.0 [1.5.1]: https://github.com/konradhalas/dacite/compare/v1.5.0...v1.5.1 [1.5.0]: https://github.com/konradhalas/dacite/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/konradhalas/dacite/compare/v1.3.0...v1.4.0 [1.3.0]: https://github.com/konradhalas/dacite/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/konradhalas/dacite/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/konradhalas/dacite/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/konradhalas/dacite/compare/v1.0.2...v1.1.0 dacite-1.6.0/LICENSE000066400000000000000000000020551376112644300137650ustar00rootroot00000000000000MIT License Copyright (c) 2018 Konrad Hałas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.dacite-1.6.0/README.md000066400000000000000000000312311376112644300142350ustar00rootroot00000000000000# dacite [![Build Status](https://travis-ci.org/konradhalas/dacite.svg?branch=master)](https://travis-ci.org/konradhalas/dacite) [![Coverage Status](https://coveralls.io/repos/github/konradhalas/dacite/badge.svg?branch=master)](https://coveralls.io/github/konradhalas/dacite?branch=master) [![License](https://img.shields.io/pypi/l/dacite.svg)](https://pypi.python.org/pypi/dacite/) [![Version](https://img.shields.io/pypi/v/dacite.svg)](https://pypi.python.org/pypi/dacite/) [![Python versions](https://img.shields.io/pypi/pyversions/dacite.svg)](https://pypi.python.org/pypi/dacite/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) This module simplifies creation of data classes ([PEP 557][pep-557]) from dictionaries. ## Installation To install dacite, simply use `pip`: ``` $ pip install dacite ``` ## Requirements Minimum Python version supported by `dacite` is 3.6. ## Quick start ```python from dataclasses import dataclass from dacite import from_dict @dataclass class User: name: str age: int is_active: bool data = { 'name': 'John', 'age': 30, 'is_active': True, } user = from_dict(data_class=User, data=data) assert user == User(name='John', age=30, is_active=True) ``` ## Features Dacite supports following features: - nested structures - (basic) types checking - optional fields (i.e. `typing.Optional`) - unions - forward references - collections - custom type hooks ## Motivation Passing plain dictionaries as a data container between your functions or methods isn't a good practice. Of course you can always create your custom class instead, but this solution is an overkill if you only want to merge a few fields within a single object. Fortunately Python has a good solution to this problem - data classes. Thanks to `@dataclass` decorator you can easily create a new custom type with a list of given fields in a declarative manner. Data classes support type hints by design. However, even if you are using data classes, you have to create their instances somehow. In many such cases, your input is a dictionary - it can be a payload from a HTTP request or a raw data from a database. If you want to convert those dictionaries into data classes, `dacite` is your best friend. This library was originally created to simplify creation of type hinted data transfer objects (DTO) which can cross the boundaries in the application architecture. It's important to mention that `dacite` is not a data validation library. There are dozens of awesome data validation projects and it doesn't make sense to duplicate this functionality within `dacite`. If you want to validate your data first, you should combine `dacite` with one of data validation library. Please check [Use Case](#use-case) section for a real-life example. ## Usage Dacite is based on a single function - `dacite.from_dict`. This function takes 3 parameters: - `data_class` - data class type - `data` - dictionary of input data - `config` (optional) - configuration of the creation process, instance of `dacite.Config` class Configuration is a (data) class with following fields: - `type_hooks` - `cast` - `forward_references` - `check_types` - `strict` - `strict_unions_match` The examples below show all features of `from_dict` function and usage of all `Config` parameters. ### Nested structures You can pass a data with nested dictionaries and it will create a proper result. ```python @dataclass class A: x: str y: int @dataclass class B: a: A data = { 'a': { 'x': 'test', 'y': 1, } } result = from_dict(data_class=B, data=data) assert result == B(a=A(x='test', y=1)) ``` ### Optional fields Whenever your data class has a `Optional` field and you will not provide input data for this field, it will take the `None` value. ```python from typing import Optional @dataclass class A: x: str y: Optional[int] data = { 'x': 'test', } result = from_dict(data_class=A, data=data) assert result == A(x='test', y=None) ``` ### Unions If your field can accept multiple types, you should use `Union`. Dacite will try to match data with provided types one by one. If none will match, it will raise `UnionMatchError` exception. ```python from typing import Union @dataclass class A: x: str @dataclass class B: y: int @dataclass class C: u: Union[A, B] data = { 'u': { 'y': 1, }, } result = from_dict(data_class=C, data=data) assert result == C(u=B(y=1)) ``` ### Collections Dacite supports fields defined as collections. It works for both - basic types and data classes. ```python @dataclass class A: x: str y: int @dataclass class B: a_list: List[A] data = { 'a_list': [ { 'x': 'test1', 'y': 1, }, { 'x': 'test2', 'y': 2, } ], } result = from_dict(data_class=B, data=data) assert result == B(a_list=[A(x='test1', y=1), A(x='test2', y=2)]) ``` ### Type hooks You can use `Config.type_hooks` argument if you want to transform the input data of a data class field with given type into the new value. You have to pass a following mapping: `{Type: callable}`, where `callable` is a `Callable[[Any], Any]`. ```python @dataclass class A: x: str data = { 'x': 'TEST', } result = from_dict(data_class=A, data=data, config=Config(type_hooks={str: str.lower})) assert result == A(x='test') ``` If a data class field type is a `Optional[T]` you can pass both - `Optional[T]` or just `T` - as a key in `type_hooks`. The same with generic collections, e.g. when a field has type `List[T]` you can use `List[T]` to transform whole collection or `T` to transform each item. ### Casting It's a very common case that you want to create an instance of a field type from the input data with just calling your type with the input value. Of course you can use `type_hooks={T: T}` to achieve this goal but `cast=[T]` is an easier and more expressive way. It also works with base classes - if `T` is a base class of type `S`, all fields of type `S` will be also "casted". ```python from enum import Enum class E(Enum): X = 'x' Y = 'y' Z = 'z' @dataclass class A: e: E data = { 'e': 'x', } result = from_dict(data_class=A, data=data, config=Config(cast=[E])) # or result = from_dict(data_class=A, data=data, config=Config(cast=[Enum])) assert result == A(e=E.X) ``` ### Forward References Definition of forward references can be passed as a `{'name': Type}` mapping to `Config.forward_references`. This dict is passed to `typing.get_type_hints()` as the `globalns` param when evaluating each field's type. ```python @dataclass class X: y: "Y" @dataclass class Y: s: str data = from_dict(X, {"y": {"s": "text"}}, Config(forward_references={"Y": Y})) assert data == X(Y("text")) ``` ### Types checking There are rare cases when `dacite` built-in type checker can not validate your types (e.g. custom generic class) or you have such functionality covered by other library and you don't want to validate your types twice. In such case you can disable type checking with `Config(check_types=False)`. By default types checking is enabled. ```python T = TypeVar('T') class X(Generic[T]): pass @dataclass class A: x: X[str] x = X[str]() assert from_dict(A, {'x': x}, config=Config(check_types=False)) == A(x=x) ``` ### Strict mode By default `from_dict` ignores additional keys (not matching data class field) in the input data. If you want change this behaviour set `Config.strict` to `True`. In case of unexpected key `from_dict` will raise `UnexpectedDataError` exception. ### Strict unions match `Union` allows to define multiple possible types for a given field. By default `dacite` is trying to find the first matching type for a provided data and it returns instance of this type. It means that it's possible that there are other matching types further on the `Union` types list. With `strict_unions_match` only a single match is allowed, otherwise `dacite` raises `StrictUnionMatchError`. ## Exceptions Whenever something goes wrong, `from_dict` will raise adequate exception. There are a few of them: - `WrongTypeError` - raised when a type of a input value does not match with a type of a data class field - `MissingValueError` - raised when you don't provide a value for a required field - `UnionMatchError` - raised when provided data does not match any type of `Union` - `ForwardReferenceError` - raised when undefined forward reference encountered in dataclass - `UnexpectedDataError` - raised when `strict` mode is enabled and the input data has not matching keys - `StrictUnionMatchError` - raised when `strict_unions_match` mode is enabled and the input data has ambiguous `Union` match ## Development First of all - if you want to submit your pull request, thank you very much! I really appreciate your support. Please remember that every new feature, bug fix or improvement should be tested. 100% code coverage is a must have. We are using a few static code analysis tools to increase the code quality (`black`, `mypy`, `pylint`). Please make sure that you are not generating any errors/warnings before you submit your PR. You can find current configuration in `.travis.yml` file. Last but not least, if you want to introduce new feature, please discuss it first within an issue. ### How to start Clone `dacite` repository: ``` $ git clone git@github.com:konradhalas/dacite.git ``` Create and activate virtualenv in the way you like: ``` $ python3 -m venv dacite-env $ source dacite-env/bin/activate ``` Install all `dacite` dependencies: ``` $ pip install -e .[dev] ``` To run tests you just have to fire: ``` $ pytest ``` ## Use case There are many cases when we receive "raw" data (Python dicts) as a input to our system. HTTP request payload is a very common use case. In most web frameworks we receive request data as a simple dictionary. Instead of passing this dict down to your "business" code, it's a good idea to create something more "robust". Following example is a simple `flask` app - it has single `/products` endpoint. You can use this endpoint to "create" product in your system. Our core `create_product` function expects data class as a parameter. Thanks to `dacite` we can easily build such data class from `POST` request payload. ```python from dataclasses import dataclass from typing import List from flask import Flask, request, Response import dacite app = Flask(__name__) @dataclass class ProductVariantData: code: str description: str = '' stock: int = 0 @dataclass class ProductData: name: str price: float variants: List[ProductVariantData] def create_product(product_data: ProductData) -> None: pass # your business logic here @app.route("/products", methods=['POST']) def products(): product_data = dacite.from_dict( data_class=ProductData, data=request.get_json(), ) create_product(product_data=product_data) return Response(status=201) ``` What if we want to validate our data (e.g. check if `code` has 6 characters)? Such features are out of scope of `dacite` but we can easily combine it with one of data validation library. Let's try with [marshmallow](https://marshmallow.readthedocs.io). First of all we have to define our data validation schemas: ```python from marshmallow import Schema, fields, ValidationError def validate_code(code): if len(code) != 6: raise ValidationError('Code must have 6 characters.') class ProductVariantDataSchema(Schema): code = fields.Str(required=True, validate=validate_code) description = fields.Str(required=False) stock = fields.Int(required=False) class ProductDataSchema(Schema): name = fields.Str(required=True) price = fields.Decimal(required=True) variants = fields.Nested(ProductVariantDataSchema(many=True)) ``` And use them within our endpoint: ```python @app.route("/products", methods=['POST']) def products(): schema = ProductDataSchema() result, errors = schema.load(request.get_json()) if errors: return Response( response=json.dumps(errors), status=400, mimetype='application/json', ) product_data = dacite.from_dict( data_class=ProductData, data=result, ) create_product(product_data=product_data) return Response(status=201) ``` Still `dacite` helps us to create data class from "raw" dict with validated data. ## Changelog Follow `dacite` updates in [CHANGELOG][changelog]. ## Authors Created by [Konrad Hałas][halas-homepage]. [pep-557]: https://www.python.org/dev/peps/pep-0557/ [halas-homepage]: https://konradhalas.pl [changelog]: https://github.com/konradhalas/dacite/blob/master/CHANGELOG.md dacite-1.6.0/dacite/000077500000000000000000000000001376112644300142075ustar00rootroot00000000000000dacite-1.6.0/dacite/__init__.py000066400000000000000000000001431376112644300163160ustar00rootroot00000000000000from dacite.config import Config from dacite.core import from_dict from dacite.exceptions import * dacite-1.6.0/dacite/config.py000066400000000000000000000006271376112644300160330ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import Dict, Any, Callable, Optional, Type, List @dataclass class Config: type_hooks: Dict[Type, Callable[[Any], Any]] = field(default_factory=dict) cast: List[Type] = field(default_factory=list) forward_references: Optional[Dict[str, Any]] = None check_types: bool = True strict: bool = False strict_unions_match: bool = False dacite-1.6.0/dacite/core.py000066400000000000000000000126231376112644300155150ustar00rootroot00000000000000import copy from dataclasses import is_dataclass from itertools import zip_longest from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any from dacite.config import Config from dacite.data import Data from dacite.dataclasses import get_default_value_for_field, create_instance, DefaultValueNotFoundError, get_fields from dacite.exceptions import ( ForwardReferenceError, WrongTypeError, DaciteError, UnionMatchError, MissingValueError, DaciteFieldError, UnexpectedDataError, StrictUnionMatchError, ) from dacite.types import ( is_instance, is_generic_collection, is_union, extract_generic, is_optional, transform_value, extract_origin_collection, is_init_var, extract_init_var, ) T = TypeVar("T") def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None) -> T: """Create a data class instance from a dictionary. :param data_class: a data class type :param data: a dictionary of a input data :param config: a configuration of the creation process :return: an instance of a data class """ init_values: Data = {} post_init_values: Data = {} config = config or Config() try: data_class_hints = get_type_hints(data_class, globalns=config.forward_references) except NameError as error: raise ForwardReferenceError(str(error)) data_class_fields = get_fields(data_class) if config.strict: extra_fields = set(data.keys()) - {f.name for f in data_class_fields} if extra_fields: raise UnexpectedDataError(keys=extra_fields) for field in data_class_fields: field = copy.copy(field) field.type = data_class_hints[field.name] try: try: field_data = data[field.name] transformed_value = transform_value( type_hooks=config.type_hooks, cast=config.cast, target_type=field.type, value=field_data ) value = _build_value(type_=field.type, data=transformed_value, config=config) except DaciteFieldError as error: error.update_path(field.name) raise if config.check_types and not is_instance(value, field.type): raise WrongTypeError(field_path=field.name, field_type=field.type, value=value) except KeyError: try: value = get_default_value_for_field(field) except DefaultValueNotFoundError: if not field.init: continue raise MissingValueError(field.name) if field.init: init_values[field.name] = value else: post_init_values[field.name] = value return create_instance(data_class=data_class, init_values=init_values, post_init_values=post_init_values) def _build_value(type_: Type, data: Any, config: Config) -> Any: if is_init_var(type_): type_ = extract_init_var(type_) if is_union(type_): return _build_value_for_union(union=type_, data=data, config=config) elif is_generic_collection(type_) and is_instance(data, extract_origin_collection(type_)): return _build_value_for_collection(collection=type_, data=data, config=config) elif is_dataclass(type_) and is_instance(data, Data): return from_dict(data_class=type_, data=data, config=config) return data def _build_value_for_union(union: Type, data: Any, config: Config) -> Any: types = extract_generic(union) if is_optional(union) and len(types) == 2: return _build_value(type_=types[0], data=data, config=config) union_matches = {} for inner_type in types: try: # noinspection PyBroadException try: data = transform_value( type_hooks=config.type_hooks, cast=config.cast, target_type=inner_type, value=data ) except Exception: # pylint: disable=broad-except continue value = _build_value(type_=inner_type, data=data, config=config) if is_instance(value, inner_type): if config.strict_unions_match: union_matches[inner_type] = value else: return value except DaciteError: pass if config.strict_unions_match: if len(union_matches) > 1: raise StrictUnionMatchError(union_matches) return union_matches.popitem()[1] if not config.check_types: return data raise UnionMatchError(field_type=union, value=data) def _build_value_for_collection(collection: Type, data: Any, config: Config) -> Any: data_type = data.__class__ if is_instance(data, Mapping): item_type = extract_generic(collection, defaults=(Any, Any))[1] return data_type((key, _build_value(type_=item_type, data=value, config=config)) for key, value in data.items()) elif is_instance(data, tuple): types = extract_generic(collection) if len(types) == 2 and types[1] == Ellipsis: return data_type(_build_value(type_=types[0], data=item, config=config) for item in data) return data_type( _build_value(type_=type_, data=item, config=config) for item, type_ in zip_longest(data, types) ) item_type = extract_generic(collection, defaults=(Any,))[0] return data_type(_build_value(type_=item_type, data=item, config=config) for item in data) dacite-1.6.0/dacite/data.py000066400000000000000000000000641376112644300154720ustar00rootroot00000000000000from typing import Dict, Any Data = Dict[str, Any] dacite-1.6.0/dacite/dataclasses.py000066400000000000000000000020241376112644300170460ustar00rootroot00000000000000from dataclasses import Field, MISSING, _FIELDS, _FIELD, _FIELD_INITVAR # type: ignore from typing import Type, Any, TypeVar, List from dacite.data import Data from dacite.types import is_optional T = TypeVar("T", bound=Any) class DefaultValueNotFoundError(Exception): pass def get_default_value_for_field(field: Field) -> Any: if field.default != MISSING: return field.default elif field.default_factory != MISSING: # type: ignore return field.default_factory() # type: ignore elif is_optional(field.type): return None raise DefaultValueNotFoundError() def create_instance(data_class: Type[T], init_values: Data, post_init_values: Data) -> T: instance = data_class(**init_values) for key, value in post_init_values.items(): setattr(instance, key, value) return instance def get_fields(data_class: Type[T]) -> List[Field]: fields = getattr(data_class, _FIELDS) return [f for f in fields.values() if f._field_type is _FIELD or f._field_type is _FIELD_INITVAR] dacite-1.6.0/dacite/exceptions.py000066400000000000000000000050331376112644300167430ustar00rootroot00000000000000from typing import Any, Type, Optional, Set, Dict def _name(type_: Type) -> str: return type_.__name__ if hasattr(type_, "__name__") else str(type_) class DaciteError(Exception): pass class DaciteFieldError(DaciteError): def __init__(self, field_path: Optional[str] = None): super().__init__() self.field_path = field_path def update_path(self, parent_field_path: str) -> None: if self.field_path: self.field_path = f"{parent_field_path}.{self.field_path}" else: self.field_path = parent_field_path class WrongTypeError(DaciteFieldError): def __init__(self, field_type: Type, value: Any, field_path: Optional[str] = None) -> None: super().__init__(field_path=field_path) self.field_type = field_type self.value = value def __str__(self) -> str: return ( f'wrong value type for field "{self.field_path}" - should be "{_name(self.field_type)}" ' f'instead of value "{self.value}" of type "{_name(type(self.value))}"' ) class MissingValueError(DaciteFieldError): def __init__(self, field_path: Optional[str] = None): super().__init__(field_path=field_path) def __str__(self) -> str: return f'missing value for field "{self.field_path}"' class UnionMatchError(WrongTypeError): def __str__(self) -> str: return ( f'can not match type "{_name(type(self.value))}" to any type ' f'of "{self.field_path}" union: {_name(self.field_type)}' ) class StrictUnionMatchError(DaciteFieldError): def __init__(self, union_matches: Dict[Type, Any], field_path: Optional[str] = None) -> None: super().__init__(field_path=field_path) self.union_matches = union_matches def __str__(self) -> str: conflicting_types = ", ".join(_name(type_) for type_ in self.union_matches) return f'can not choose between possible Union matches for field "{self.field_path}": {conflicting_types}' class ForwardReferenceError(DaciteError): def __init__(self, message: str) -> None: super().__init__() self.message = message def __str__(self) -> str: return f"can not resolve forward reference: {self.message}" class UnexpectedDataError(DaciteError): def __init__(self, keys: Set[str]) -> None: super().__init__() self.keys = keys def __str__(self) -> str: formatted_keys = ", ".join(f'"{key}"' for key in self.keys) return f"can not match {formatted_keys} to any data class field" dacite-1.6.0/dacite/py.typed000066400000000000000000000000001376112644300156740ustar00rootroot00000000000000dacite-1.6.0/dacite/types.py000066400000000000000000000132771376112644300157370ustar00rootroot00000000000000from dataclasses import InitVar from typing import Type, Any, Optional, Union, Collection, TypeVar, Dict, Callable, Mapping, List, Tuple T = TypeVar("T", bound=Any) def transform_value( type_hooks: Dict[Type, Callable[[Any], Any]], cast: List[Type], target_type: Type, value: Any ) -> Any: if target_type in type_hooks: value = type_hooks[target_type](value) else: for cast_type in cast: if is_subclass(target_type, cast_type): if is_generic_collection(target_type): value = extract_origin_collection(target_type)(value) else: value = target_type(value) break if is_optional(target_type): if value is None: return None target_type = extract_optional(target_type) return transform_value(type_hooks, cast, target_type, value) if is_generic_collection(target_type) and isinstance(value, extract_origin_collection(target_type)): collection_cls = value.__class__ if issubclass(collection_cls, dict): key_cls, item_cls = extract_generic(target_type, defaults=(Any, Any)) return collection_cls( { transform_value(type_hooks, cast, key_cls, key): transform_value(type_hooks, cast, item_cls, item) for key, item in value.items() } ) item_cls = extract_generic(target_type, defaults=(Any,))[0] return collection_cls(transform_value(type_hooks, cast, item_cls, item) for item in value) return value def extract_origin_collection(collection: Type) -> Type: try: return collection.__extra__ except AttributeError: return collection.__origin__ def is_optional(type_: Type) -> bool: return is_union(type_) and type(None) in extract_generic(type_) def extract_optional(optional: Type[Optional[T]]) -> T: for type_ in extract_generic(optional): if type_ is not type(None): return type_ raise ValueError("can not find not-none value") def is_generic(type_: Type) -> bool: return hasattr(type_, "__origin__") def is_union(type_: Type) -> bool: return is_generic(type_) and type_.__origin__ == Union def is_literal(type_: Type) -> bool: try: from typing import Literal # type: ignore return is_generic(type_) and type_.__origin__ == Literal except ImportError: return False def is_new_type(type_: Type) -> bool: return hasattr(type_, "__supertype__") def extract_new_type(type_: Type) -> Type: return type_.__supertype__ def is_init_var(type_: Type) -> bool: return isinstance(type_, InitVar) or type_ is InitVar def extract_init_var(type_: Type) -> Union[Type, Any]: try: return type_.type except AttributeError: return Any def is_instance(value: Any, type_: Type) -> bool: if type_ == Any: return True elif is_union(type_): return any(is_instance(value, t) for t in extract_generic(type_)) elif is_generic_collection(type_): origin = extract_origin_collection(type_) if not isinstance(value, origin): return False if not extract_generic(type_): return True if isinstance(value, tuple): tuple_types = extract_generic(type_) if len(tuple_types) == 1 and tuple_types[0] == (): return len(value) == 0 elif len(tuple_types) == 2 and tuple_types[1] is ...: return all(is_instance(item, tuple_types[0]) for item in value) else: if len(tuple_types) != len(value): return False return all(is_instance(item, item_type) for item, item_type in zip(value, tuple_types)) if isinstance(value, Mapping): key_type, val_type = extract_generic(type_, defaults=(Any, Any)) for key, val in value.items(): if not is_instance(key, key_type) or not is_instance(val, val_type): return False return True return all(is_instance(item, extract_generic(type_, defaults=(Any,))[0]) for item in value) elif is_new_type(type_): return is_instance(value, extract_new_type(type_)) elif is_literal(type_): return value in extract_generic(type_) elif is_init_var(type_): return is_instance(value, extract_init_var(type_)) elif is_type_generic(type_): return is_subclass(value, extract_generic(type_)[0]) else: try: # As described in PEP 484 - section: "The numeric tower" if isinstance(value, (int, float)) and type_ in [float, complex]: return True return isinstance(value, type_) except TypeError: return False def is_generic_collection(type_: Type) -> bool: if not is_generic(type_): return False origin = extract_origin_collection(type_) try: return bool(origin and issubclass(origin, Collection)) except (TypeError, AttributeError): return False def extract_generic(type_: Type, defaults: Tuple = ()) -> tuple: try: if hasattr(type_, "_special") and type_._special: return defaults return type_.__args__ or defaults # type: ignore except AttributeError: return defaults def is_subclass(sub_type: Type, base_type: Type) -> bool: if is_generic_collection(sub_type): sub_type = extract_origin_collection(sub_type) try: return issubclass(sub_type, base_type) except TypeError: return False def is_type_generic(type_: Type) -> bool: try: return type_.__origin__ in (type, Type) except AttributeError: return False dacite-1.6.0/pyproject.toml000066400000000000000000000000371376112644300156720ustar00rootroot00000000000000[tool.black] line-length = 120 dacite-1.6.0/setup.py000066400000000000000000000022541376112644300144730ustar00rootroot00000000000000from setuptools import setup setup( name="dacite", version="1.6.0", description="Simple creation of data classes from dictionaries.", long_description=open("README.md").read(), long_description_content_type="text/markdown", author="Konrad Hałas", author_email="halas.konrad@gmail.com", url="https://github.com/konradhalas/dacite", license="MIT", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", ], python_requires=">=3.6", keywords="dataclasses", packages=["dacite"], package_data={"dacite": ["py.typed"]}, install_requires=['dataclasses;python_version<"3.7"'], extras_require={"dev": ["pytest>=5", "pytest-cov", "coveralls", "black", "mypy", "pylint"]}, ) dacite-1.6.0/tests/000077500000000000000000000000001376112644300141205ustar00rootroot00000000000000dacite-1.6.0/tests/__init__.py000066400000000000000000000000001376112644300162170ustar00rootroot00000000000000dacite-1.6.0/tests/common.py000066400000000000000000000002211376112644300157550ustar00rootroot00000000000000import sys import pytest literal_support = init_var_type_support = pytest.mark.skipif(sys.version_info < (3, 8), reason="requires Python 3.8") dacite-1.6.0/tests/core/000077500000000000000000000000001376112644300150505ustar00rootroot00000000000000dacite-1.6.0/tests/core/__init__.py000066400000000000000000000000001376112644300171470ustar00rootroot00000000000000dacite-1.6.0/tests/core/test_base.py000066400000000000000000000063621376112644300174020ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import Any, NewType import pytest from dacite import from_dict, MissingValueError, WrongTypeError def test_from_dict_with_correct_data(): @dataclass class X: s: str i: int f: float result = from_dict(X, {"s": "test", "i": 1, "f": 1.0}) assert result == X(s="test", i=1, f=1.0) def test_from_dict_with_default_value(): @dataclass class X: s: str i: int = 0 result = from_dict(X, {"s": "test"}) assert result == X(s="test", i=0) def test_from_dict_with_default_factory(): @dataclass class X: s: str i: int = field(default_factory=lambda: 42) result = from_dict(X, {"s": "test"}) assert result == X(s="test", i=42) def test_from_dict_with_wrong_type(): @dataclass class X: s: str i: int with pytest.raises(WrongTypeError) as exception_info: from_dict(X, {"s": "test", "i": "wrong"}) assert ( str(exception_info.value) == 'wrong value type for field "i" - should be "int" instead of value "wrong" of type "str"' ) assert exception_info.value.field_path == "i" assert exception_info.value.field_type == int assert exception_info.value.value == "wrong" def test_from_dict_with_missing_value(): @dataclass class X: s: str i: int with pytest.raises(MissingValueError) as exception_info: from_dict(X, {"s": "test"}) assert str(exception_info.value) == 'missing value for field "i"' assert exception_info.value.field_path == "i" def test_from_dict_with_nested_data_class(): @dataclass class X: i: int @dataclass class Y: s: str x: X result = from_dict(Y, {"s": "test", "x": {"i": 1}}) assert result == Y(s="test", x=X(i=1)) def test_from_dict_with_missing_value_of_nested_data_class(): @dataclass class X: i: int @dataclass class Y: x: X with pytest.raises(MissingValueError) as exception_info: from_dict(Y, {"x": {}}) assert exception_info.value.field_path == "x.i" def test_from_dict_with_additional_values(): @dataclass class X: i: int result = from_dict(X, {"i": 1, "s": "extra"}) assert result == X(i=1) def test_from_dict_with_any(): @dataclass class X: i: Any result = from_dict(X, {"i": 1}) assert result == X(i=1) def test_from_dict_with_nested_data_classes_and_default_factory(): @dataclass class X: i: int @dataclass class Y: x: X = field(default_factory=lambda: X(i=42)) result = from_dict(Y, {}) assert result == Y(x=X(i=42)) def test_from_dict_with_post_init(): @dataclass class X: s: str = field(init=False) x = X() x.s = "test" result = from_dict(X, {"s": "test"}) assert result == x def test_from_dict_with_post_init_missing_value(): @dataclass class X: s: str = field(init=False) result = from_dict(X, {}) assert not hasattr(result, "s") def test_from_dict_with_new_type(): MyStr = NewType("MyStr", str) @dataclass class X: s: MyStr result = from_dict(X, {"s": "test"}) assert result == X(s=MyStr("test")) dacite-1.6.0/tests/core/test_collection.py000066400000000000000000000103241376112644300206140ustar00rootroot00000000000000from dataclasses import dataclass from typing import List, Set, Union, Dict, Collection, Tuple import pytest from dacite import from_dict, WrongTypeError def test_from_dict_with_generic_collection(): @dataclass class X: l: List[int] result = from_dict(X, {"l": [1]}) assert result == X(l=[1]) def test_from_dict_with_generic_collection_of_data_classes(): @dataclass class X: i: int @dataclass class Y: x_list: List[X] result = from_dict(Y, {"x_list": [{"i": 1}, {"i": 2}]}) assert result == Y(x_list=[X(i=1), X(i=2)]) def test_from_dict_with_generic_collection_of_unions(): @dataclass class X: i: int @dataclass class Y: l: List[Union[int, X]] result = from_dict(Y, {"l": [1, {"i": 2}]}) assert result == Y(l=[1, X(i=2)]) def test_from_dict_with_nested_generic_collection(): @dataclass class X: i: int @dataclass class Y: l: List[List[X]] result = from_dict(Y, {"l": [[{"i": 2}]]}) assert result == Y(l=[[X(i=2)]]) def test_from_dict_with_set(): @dataclass class X: i_set: Set[int] result = from_dict(X, {"i_set": {1, 2}}) assert result == X(i_set={1, 2}) def test_from_dict_with_dict(): @dataclass class X: d: Dict[str, int] result = from_dict(X, {"d": {"a": 1, "b": 2}}) assert result == X(d={"a": 1, "b": 2}) def test_from_dict_with_dict_of_data_classes(): @dataclass class X: i: int @dataclass class Y: d: Dict[str, X] result = from_dict(Y, {"d": {"a": {"i": 42}, "b": {"i": 37}}}) assert result == Y(d={"a": X(i=42), "b": X(i=37)}) def test_from_dict_with_already_created_data_class_instances(): @dataclass class X: i: int @dataclass class Y: x: X x_list: List[X] result = from_dict(Y, {"x": X(i=37), "x_list": [X(i=42)]}) assert result == Y(x=X(i=37), x_list=[X(i=42)]) def test_from_dict_with_generic_abstract_collection(): @dataclass class X: l: Collection[int] result = from_dict(X, {"l": [1]}) assert result == X(l=[1]) def test_from_dict_with_wrong_type_of_collection_item(): @dataclass class X: l: List[int] with pytest.raises(WrongTypeError) as exception_info: from_dict(X, {"l": ["1"]}) assert exception_info.value.field_path == "l" assert exception_info.value.field_type == List[int] def test_from_dict_with_wrong_type_of_dict_value(): @dataclass class X: d: Dict[str, int] with pytest.raises(WrongTypeError) as exception_info: from_dict(X, {"d": {"a": "test"}}) assert exception_info.value.field_path == "d" assert exception_info.value.field_type == Dict[str, int] def test_from_dict_with_dict_and_implicit_any_types(): @dataclass class X: d: Dict result = from_dict(X, {"d": {"a": 1}}) assert result == X(d={"a": 1}) def test_from_dict_with_list_and_implicit_any_types(): @dataclass class X: l: List result = from_dict(X, {"l": [1]}) assert result == X(l=[1]) def test_from_dict_with_tuple_of_defined_length(): @dataclass class X: a: int @dataclass class Y: b: int @dataclass class Z: t: Tuple[X, Y] result = from_dict(Z, {"t": ({"a": 1}, {"b": 2})}) assert result == Z(t=(X(a=1), Y(b=2))) def test_from_dict_with_tuple_of_undefined_length(): @dataclass class X: a: int @dataclass class Y: t: Tuple[X, ...] result = from_dict(Y, {"t": ({"a": 1}, {"a": 2})}) assert result == Y(t=(X(a=1), X(a=2))) def test_from_dict_with_tuple_and_wrong_length(): @dataclass class X: a: int @dataclass class Y: b: int @dataclass class Z: t: Tuple[X, Y] with pytest.raises(WrongTypeError) as exception_info: from_dict(Z, {"t": ({"a": 1}, {"b": 2}, {"c": 3})}) assert exception_info.value.field_path == "t" assert exception_info.value.field_type == Tuple[X, Y] def test_from_dict_with_tuple_and_implicit_any_types(): @dataclass class X: t: Tuple result = from_dict(X, {"t": (1, 2, 3)}) assert result == X(t=(1, 2, 3)) dacite-1.6.0/tests/core/test_config.py000066400000000000000000000100711376112644300177250ustar00rootroot00000000000000from dataclasses import dataclass from enum import Enum from typing import Optional, List, Union import pytest from dacite import ( from_dict, Config, ForwardReferenceError, UnexpectedDataError, StrictUnionMatchError, ) def test_from_dict_with_type_hooks(): @dataclass class X: s: str result = from_dict(X, {"s": "TEST"}, Config(type_hooks={str: str.lower})) assert result == X(s="test") def test_from_dict_with_type_hooks_and_optional(): @dataclass class X: s: Optional[str] result = from_dict(X, {"s": "TEST"}, Config(type_hooks={str: str.lower})) assert result == X(s="test") def test_from_dict_with_type_hooks_and_union(): @dataclass class X: s: Union[str, int] result = from_dict(X, {"s": "TEST"}, Config(type_hooks={str: str.lower})) assert result == X(s="test") def test_from_dict_with_cast(): @dataclass class X: s: str result = from_dict(X, {"s": 1}, Config(cast=[str])) assert result == X(s="1") def test_from_dict_with_base_class_cast(): class E(Enum): A = "a" @dataclass class X: e: E result = from_dict(X, {"e": "a"}, Config(cast=[Enum])) assert result == X(e=E.A) def test_from_dict_with_base_class_cast_and_optional(): class E(Enum): A = "a" @dataclass class X: e: Optional[E] result = from_dict(X, {"e": "a"}, Config(cast=[Enum])) assert result == X(e=E.A) def test_from_dict_with_cast_and_generic_collection(): @dataclass class X: s: List[int] result = from_dict(X, {"s": (1,)}, Config(cast=[List])) assert result == X(s=[1]) def test_from_dict_with_type_hooks_and_generic_sequence(): @dataclass class X: c: List[str] result = from_dict(X, {"c": ["TEST"]}, config=Config(type_hooks={str: str.lower})) assert result == X(c=["test"]) def test_from_dict_with_forward_reference(): @dataclass class X: y: "Y" @dataclass class Y: s: str data = from_dict(X, {"y": {"s": "text"}}, Config(forward_references={"Y": Y})) assert data == X(Y("text")) def test_from_dict_with_missing_forward_reference(): @dataclass class X: y: "Y" @dataclass class Y: s: str with pytest.raises(ForwardReferenceError) as exception_info: from_dict(X, {"y": {"s": "text"}}) assert str(exception_info.value) == "can not resolve forward reference: name 'Y' is not defined" def test_form_dict_with_disabled_type_checking(): @dataclass class X: i: int result = from_dict(X, {"i": "test"}, config=Config(check_types=False)) # noinspection PyTypeChecker assert result == X(i="test") def test_form_dict_with_disabled_type_checking_and_union(): @dataclass class X: i: Union[int, float] result = from_dict(X, {"i": "test"}, config=Config(check_types=False)) # noinspection PyTypeChecker assert result == X(i="test") def test_from_dict_with_strict(): @dataclass class X: s: str with pytest.raises(UnexpectedDataError) as exception_info: from_dict(X, {"s": "test", "i": 1}, Config(strict=True)) assert str(exception_info.value) == 'can not match "i" to any data class field' def test_from_dict_with_strict_unions_match_and_ambiguous_match(): @dataclass class X: i: int @dataclass class Y: i: int @dataclass class Z: u: Union[X, Y] data = { "u": {"i": 1}, } with pytest.raises(StrictUnionMatchError) as exception_info: from_dict(Z, data, Config(strict_unions_match=True)) assert str(exception_info.value) == 'can not choose between possible Union matches for field "u": X, Y' def test_from_dict_with_strict_unions_match_and_single_match(): @dataclass class X: f: str @dataclass class Y: f: int @dataclass class Z: u: Union[X, Y] data = { "u": {"f": 1}, } result = from_dict(Z, data, Config(strict_unions_match=True)) assert result == Z(u=Y(f=1)) dacite-1.6.0/tests/core/test_init_var.py000066400000000000000000000013601376112644300202740ustar00rootroot00000000000000from dataclasses import dataclass, InitVar, field from dacite import from_dict from tests.common import init_var_type_support def test_from_dict_with_init_var(): @dataclass class X: a: InitVar[int] b: int = field(init=False) def __post_init__(self, a: int) -> None: self.b = 2 * a result = from_dict(X, {"a": 2}) assert result.b == 4 @init_var_type_support def test_from_dict_with_init_var_and_data_class(): @dataclass class X: i: int @dataclass class Y: a: InitVar[X] b: X = field(init=False) def __post_init__(self, a: X) -> None: self.b = X(i=2 * a.i) result = from_dict(Y, {"a": {"i": 2}}) assert result.b == X(i=4) dacite-1.6.0/tests/core/test_literal.py000066400000000000000000000021711376112644300201160ustar00rootroot00000000000000from dataclasses import dataclass from typing import Optional import pytest from dacite import from_dict from dacite.exceptions import WrongTypeError from tests.common import literal_support @literal_support def test_from_dict_with_literal(): from typing import Literal @dataclass class X: l: Literal["A", "B"] result = from_dict(X, {"l": "A"}) assert result == X(l="A") @literal_support def test_from_dict_with_literal_and_wrong_value(): from typing import Literal @dataclass class X: l: Literal["A", "B"] with pytest.raises(WrongTypeError) as exception_info: from_dict(X, {"l": "C"}) @literal_support def test_from_dict_with_optional_literal_and_none(): from typing import Literal @dataclass class X: l: Optional[Literal["A", "B"]] result = from_dict(X, {"l": None}) assert result == X(l=None) @literal_support def test_from_dict_with_optional_literal_and_not_none(): from typing import Literal @dataclass class X: l: Optional[Literal["A", "B"]] result = from_dict(X, {"l": "A"}) assert result == X(l="A") dacite-1.6.0/tests/core/test_optional.py000066400000000000000000000067461376112644300203230ustar00rootroot00000000000000from dataclasses import dataclass from typing import Optional, Union, List, NewType import pytest from dacite import from_dict, MissingValueError, WrongTypeError def test_from_dict_with_missing_optional_value(): @dataclass class X: s: Optional[str] i: int result = from_dict(X, {"i": 1}) assert result == X(s=None, i=1) def test_from_dict_with_existing_optional_value(): @dataclass class X: s: Optional[str] i: int result = from_dict(X, {"s": "test", "i": 1}) assert result == X(s="test", i=1) def test_from_dict_with_missing_optional_value_for_union(): @dataclass class X: i: Optional[Union[int, str]] result = from_dict(X, {}) assert result == X(i=None) def test_from_dict_with_none_optional_value_for_union(): @dataclass class X: i: Optional[Union[int, str]] result = from_dict(X, {"i": None}) assert result == X(i=None) def test_from_dict_with_none_as_optional_value(): @dataclass class X: s: Optional[str] i: int result = from_dict(X, {"s": None, "i": 1}) assert result == X(s=None, i=1) def test_from_dict_with_wrong_type_of_optional_value(): @dataclass class X: s: Optional[str] i: int with pytest.raises(WrongTypeError) as exception_info: from_dict(X, {"s": 1, "i": 1}) assert exception_info.value.field_path == "s" assert exception_info.value.field_type == Optional[str] def test_from_dict_with_missing_optional_nested_data_class(): @dataclass class X: i: int @dataclass class Y: x: Optional[X] result = from_dict(Y, {}) assert result == Y(x=None) def test_from_dict_with_optional_nested_data_class(): @dataclass class X: i: int @dataclass class Y: x: Optional[X] result = from_dict(Y, {"x": {"i": 1}}) assert result == Y(x=X(i=1)) def test_from_dict_with_optional_nested_data_class_and_missing_value(): @dataclass class X: i: int j: int @dataclass class Y: x: Optional[X] with pytest.raises(MissingValueError) as exception_info: from_dict(Y, {"x": {"i": 1}}) assert exception_info.value.field_path == "x.j" def test_from_dict_with_null_as_optional_value_for_nested_data_class(): @dataclass class X: i: int @dataclass class Y: x: Optional[X] result = from_dict(Y, {"x": None}) assert result == Y(x=None) def test_from_dict_with_none_for_non_optional_field(): @dataclass class X: s: str with pytest.raises(WrongTypeError) as exception_info: from_dict(X, {"s": None}) assert exception_info.value.field_path == "s" assert exception_info.value.field_type == str assert exception_info.value.value is None def test_from_dict_with_optional_generic_collection_of_data_classes(): @dataclass class X: i: int @dataclass class Y: x_list: Optional[List[X]] result = from_dict(Y, {"x_list": [{"i": 1}, {"i": 2}]}) assert result == Y(x_list=[X(i=1), X(i=2)]) def test_from_dict_with_optional_field_and_default_value(): @dataclass class X: i: Optional[int] = 1 result = from_dict(X, {}) assert result == X(i=1) def test_from_dict_with_optional_new_type(): MyStr = NewType("MyStr", str) @dataclass class X: s: Optional[MyStr] result = from_dict(X, {"s": MyStr("test")}) assert result == X(s=MyStr("test")) dacite-1.6.0/tests/core/test_type.py000066400000000000000000000003671376112644300174500ustar00rootroot00000000000000from dataclasses import dataclass from typing import Type from dacite import from_dict def test_from_dict_with_type_field(): @dataclass class X: t: Type[int] result = from_dict(X, {"t": int}) assert result == X(t=int) dacite-1.6.0/tests/core/test_union.py000066400000000000000000000076301376112644300176170ustar00rootroot00000000000000from dataclasses import dataclass from typing import Optional, List, Union, Dict import pytest from dacite import from_dict, UnionMatchError def test_from_dict_with_union_of_builtin_types(): @dataclass class X: i: Union[int, str] result = from_dict(X, {"i": "s"}) assert result == X(i="s") def test_from_dict_with_union_of_data_classes(): @dataclass class X: i: int @dataclass class Y: s: str @dataclass class Z: x_or_y: Union[X, Y] result = from_dict(Z, {"x_or_y": {"s": "test"}}) assert result == Z(x_or_y=Y(s="test")) def test_from_dict_with_union_and_wrong_data(): @dataclass class X: i: Union[int, str] with pytest.raises(UnionMatchError) as exception_info: from_dict(X, {"i": 1.0}) assert str(exception_info.value) == 'can not match type "float" to any type of "i" union: typing.Union[int, str]' assert exception_info.value.field_path == "i" assert exception_info.value.field_type == Union[int, str] assert exception_info.value.value == 1.0 def test_from_dict_with_union_of_data_classes_and_wrong_data(): @dataclass class X: i: int @dataclass class Y: s: str @dataclass class Z: x_or_y: Union[X, Y] with pytest.raises(UnionMatchError) as exception_info: from_dict(Z, {"x_or_y": {"f": 2.0}}) assert exception_info.value.field_path == "x_or_y" assert exception_info.value.field_type == Union[X, Y] assert exception_info.value.value == {"f": 2.0} def test_from_dict_with_union_of_generic_collecionts_of_data_classes(): @dataclass class X: i: int @dataclass class Y: s: str @dataclass class Z: x_or_y: Union[List[X], List[Y]] result = from_dict(Z, {"x_or_y": [{"s": "test"}]}) assert result == Z(x_or_y=[Y(s="test")]) def test_from_dict_with_union_and_optional(): @dataclass class X: i: Union[int, Optional[str]] result = from_dict(X, {"i": "s"}) assert result == X(i="s") def test_from_dict_with_union_and_optional_and_missing_value(): @dataclass class X: i: Union[int, Optional[str]] result = from_dict(X, {}) assert result == X(i=None) def test_from_dict_with_union_and_optional_and_none_value(): @dataclass class X: i: Union[int, Optional[str]] result = from_dict(X, {"i": None}) assert result == X(i=None) def test_from_dict_with_union_and_optional_and_wrong_value(): @dataclass class X: i: Union[int, Optional[str]] with pytest.raises(UnionMatchError) as exception_info: from_dict(X, {"i": 1.0}) assert exception_info.value.field_path == "i" assert exception_info.value.field_type == Union[int, str, None] assert exception_info.value.value == 1.0 def test_from_dict_with_union_of_mixed_types_and_builtin_type_as_a_result(): @dataclass class X: i: int @dataclass class Y: u: Union[X, List[X], str] result = from_dict(Y, {"u": "test"}) assert result == Y(u="test") def test_from_dict_with_union_of_mixed_types_and_data_class_as_a_result(): @dataclass class X: i: int @dataclass class Y: u: Union[str, List[X], X] result = from_dict(Y, {"u": {"i": 1}}) assert result == Y(u=X(i=1)) def test_from_dict_with_union_of_mixed_types_and_collection_of_data_classes_as_a_result(): @dataclass class X: i: int @dataclass class Y: u: Union[str, X, List[X]] result = from_dict(Y, {"u": [{"i": 1}]}) assert result == Y(u=[X(i=1)]) def test_from_dict_with_union_of_mixed_types_and_dict_of_data_classes_as_a_result(): @dataclass class X: i: int @dataclass class Y: d: Union[int, List[X], Dict[str, X]] result = from_dict(Y, {"d": {"x": {"i": 42}, "z": {"i": 37}}}) assert result == Y(d={"x": X(i=42), "z": X(i=37)}) dacite-1.6.0/tests/test_dataclasses.py000066400000000000000000000027531376112644300200270ustar00rootroot00000000000000from dataclasses import dataclass, fields, field from typing import Optional import pytest from dacite.dataclasses import get_default_value_for_field, create_instance, DefaultValueNotFoundError def test_get_default_value_for_field_with_default_value(): @dataclass class X: i: int = 1 value = get_default_value_for_field(field=fields(X)[0]) assert value == 1 def test_get_default_value_for_field_with_default_factory(): @dataclass class X: i: int = field(default_factory=lambda: 1) value = get_default_value_for_field(field=fields(X)[0]) assert value == 1 def test_get_default_value_for_optional_field(): @dataclass class X: i: Optional[int] value = get_default_value_for_field(field=fields(X)[0]) assert value is None def test_get_default_value_for_field_without_default_value(): @dataclass class X: i: int with pytest.raises(DefaultValueNotFoundError): get_default_value_for_field(field=fields(X)[0]) def test_create_instance_with_simple_data_class(): @dataclass class X: i: int instance = create_instance(data_class=X, init_values={"i": 1}, post_init_values={}) assert instance == X(i=1) def test_create_instance_with_post_init_values(): @dataclass class X: i: int j: int = field(init=False) instance = create_instance(data_class=X, init_values={"i": 1}, post_init_values={"j": 2}) assert instance.i == 1 assert instance.j == 2 dacite-1.6.0/tests/test_types.py000066400000000000000000000240521376112644300167000ustar00rootroot00000000000000from dataclasses import InitVar from typing import Optional, Union, List, Any, Dict, NewType, TypeVar, Generic, Collection, Tuple, Type import pytest from dacite.types import ( is_optional, extract_optional, is_generic, is_union, is_generic_collection, extract_origin_collection, is_instance, extract_generic, is_new_type, extract_new_type, transform_value, is_literal, is_init_var, extract_init_var, is_type_generic, ) from tests.common import literal_support, init_var_type_support def test_is_union_with_union(): assert is_union(Union[int, float]) def test_is_union_with_non_union(): assert not is_union(int) @literal_support def test_is_literal_with_literal(): from typing import Literal assert is_literal(Literal["A", "B"]) def test_is_literal_with_non_literal(): assert not is_literal(int) def test_is_init_var_with_init_var(): assert is_init_var(InitVar[int]) def test_is_init_var_with_non_init_var(): assert not is_init_var(int) def test_is_optional_with_optional(): assert is_optional(Optional[int]) def test_is_optional_with_non_optional(): assert not is_optional(int) def test_is_optional_with_optional_of_union(): assert is_optional(Optional[Union[int, float]]) def test_extract_optional(): assert extract_optional(Optional[int]) == int def test_extract_optional_with_wrong_type(): with pytest.raises(ValueError): extract_optional(List[None]) def test_is_generic_with_generic(): assert is_generic(Optional[int]) def test_is_generic_with_non_generic(): assert not is_generic(int) def test_is_generic_collection_with_generic_collection(): assert is_generic_collection(List[int]) def test_is_generic_collection_with_non_generic_collection(): assert not is_generic_collection(list) def test_is_generic_collection_with_union(): assert not is_generic_collection(Union[int, str]) def test_extract_generic_collection(): assert extract_origin_collection(List[int]) == list def test_is_new_type_with_new_type(): assert is_new_type(NewType("NewType", int)) def test_is_new_type_with_non_new_type(): assert not is_new_type(int) def test_extract_new_type(): assert extract_new_type(NewType("NewType", int)) == int def test_is_instance_with_built_in_type_and_matching_value_type(): assert is_instance(1, int) def test_is_instance_with_built_in_type_and_not_matching_value_type(): assert not is_instance("test", int) def test_is_instance_with_union_and_matching_value_type(): assert is_instance(1, Union[int, float]) def test_is_instance_with_union_and_not_matching_value_type(): assert not is_instance("test", Union[int, float]) def test_is_instance_with_union_and_matching_generic_collection(): assert is_instance(["test"], Union[int, List[str]]) def test_is_instance_with_union_and_not_matching_generic_collection(): assert not is_instance(["test"], Union[int, List[int]]) def test_is_instance_with_optional_and_matching_value_type(): assert is_instance(1, Optional[int]) def test_is_instance_with_optional_and_not_matching_value_type(): assert not is_instance(1, Optional[str]) def test_is_instance_with_generic_collection_and_matching_value_type(): assert is_instance([1], List[int]) def test_is_instance_with_generic_collection_and_not_matching_item_type(): assert not is_instance(["test"], List[int]) def test_is_instance_with_nested_generic_collection_and_matching_value_type(): assert is_instance([[1]], List[List[int]]) def test_is_instance_with_nested_generic_collection_and_not_matching_item_type(): assert not is_instance([["test"]], List[List[int]]) def test_is_instance_with_generic_collection_without_specified_inner_types_and_matching_value_type(): assert is_instance([1], List) def test_is_instance_with_generic_collection_without_specified_inner_types_and_not_matching_value_type(): assert not is_instance([1], Dict) def test_is_instance_with_generic_abstract_collection_and_matching_value_type(): assert is_instance([1], Collection[int]) def test_is_instance_with_generic_collection_and_not_matching_value_type(): assert not is_instance({1}, List[int]) def test_is_instance_with_any_type(): assert is_instance(1, Any) def test_is_instance_with_new_type_and_matching_value_type(): assert is_instance("test", NewType("MyStr", str)) def test_is_instance_with_new_type_and_not_matching_value_type(): assert not is_instance(1, NewType("MyStr", str)) @init_var_type_support def test_is_instance_with_init_var_and_matching_value_type(): assert is_instance(1, InitVar[int]) @init_var_type_support def test_is_instance_with_init_var_and_not_matching_value_type(): assert not is_instance(1, InitVar[str]) def test_is_instance_with_with_type_and_matching_value_type(): assert is_instance(str, Type[str]) def test_is_instance_with_with_type_and_not_matching_value_type(): assert not is_instance(1, Type[str]) def test_is_instance_with_not_supported_generic_types(): T = TypeVar("T") class X(Generic[T]): pass assert not is_instance(X[str](), X[str]) def test_is_instance_with_generic_mapping_and_matching_value_type(): assert is_instance({"test": 1}, Dict[str, int]) def test_is_instance_with_generic_mapping_and_not_matching_mapping_key_type(): assert not is_instance({1: 1}, Dict[str, int]) def test_is_instance_with_generic_mapping_and_not_matching_mapping_value_type(): assert not is_instance({"test": "test"}, Dict[str, int]) def test_is_instance_with_numeric_tower(): assert is_instance(1, float) def test_is_instance_with_numeric_tower_and_optional(): assert is_instance(1, Optional[float]) def test_is_instance_with_numeric_tower_and_new_type(): assert is_instance(1, NewType("NewType", float)) @literal_support def test_is_instance_with_literal_and_matching_type(): from typing import Literal assert is_instance("A", Literal["A", "B"]) @literal_support def test_is_instance_with_literal_and_not_matching_type(): from typing import Literal assert not is_instance("C", Literal["A", "B"]) @literal_support def test_is_instance_with_optional_literal_and_matching_type(): from typing import Literal assert is_instance("A", Optional[Literal["A", "B"]]) @literal_support def test_is_instance_with_optional_literal_and_not_matching_type(): from typing import Literal assert not is_instance("C", Optional[Literal["A", "B"]]) @literal_support def test_is_instance_with_optional_literal_and_none(): from typing import Literal assert is_instance(None, Optional[Literal["A", "B"]]) def test_is_instance_with_tuple_and_matching_type(): assert is_instance((1, "test"), Tuple[int, str]) def test_is_instance_with_tuple_and_not_matching_type(): assert not is_instance((1, 2), Tuple[int, str]) def test_is_instance_with_tuple_and_wrong_length(): assert not is_instance((1, "test", 2), Tuple[int, str]) def test_is_instance_with_variable_length_tuple_and_matching_type(): assert is_instance((1, 2, 3), Tuple[int, ...]) def test_is_instance_with_variable_length_tuple_and_not_matching_type(): assert not is_instance((1, 2, "test"), Tuple[int, ...]) def test_is_instance_with_empty_tuple_and_matching_type(): assert is_instance((), Tuple[()]) def test_is_instance_with_empty_tuple_and_not_matching_type(): assert not is_instance((1, 2), Tuple[()]) def test_extract_generic(): assert extract_generic(List[int]) == (int,) def test_extract_generic_with_defaults(): assert extract_generic(List, defaults=(Any,)) == (Any,) def test_transform_value_without_matching_type(): assert transform_value({}, [], str, 1) == 1 def test_transform_value_with_matching_type(): assert transform_value({int: lambda x: x + 1}, [], int, 1) == 2 def test_transform_value_with_optional_and_not_none_value(): assert transform_value({str: str}, [], Optional[str], 1) == "1" def test_transform_value_with_optional_and_none_value(): assert transform_value({str: str}, [], Optional[str], None) is None def test_transform_value_with_optional_and_exact_matching_type(): assert transform_value({Optional[str]: str}, [], Optional[str], None) == "None" def test_transform_value_with_generic_sequence_and_matching_item(): assert transform_value({str: str}, [], List[str], [1]) == ["1"] def test_transform_value_with_generic_sequence_and_matching_sequence(): assert transform_value({List[int]: lambda x: list(reversed(x))}, [], List[int], [1, 2]) == [2, 1] def test_transform_value_with_generic_sequence_and_matching_both_item_and_sequence(): assert transform_value({List[int]: lambda x: list(reversed(x)), int: int}, [], List[int], ["1", "2"]) == [2, 1] def test_transform_value_without_matching_generic_sequence(): assert transform_value({}, [], List[int], {1}) == {1} def test_transform_value_with_nested_generic_sequence(): assert transform_value({str: str}, [], List[List[str]], [[1]]) == [["1"]] def test_transform_value_with_generic_abstract_collection(): assert transform_value({str: str}, [], Collection[str], [1]) == ["1"] def test_transform_value_with_generic_mapping(): assert transform_value({str: str, int: int}, [], Dict[str, int], {1: "2"}) == {"1": 2} def test_transform_value_with_nested_generic_mapping(): assert transform_value({str: str, int: int}, [], Dict[str, Dict[str, int]], {1: {2: "3"}}) == {"1": {"2": 3}} def test_transform_value_with_new_type(): MyStr = NewType("MyStr", str) assert transform_value({MyStr: str.upper, str: str.lower}, [], MyStr, "Test") == "TEST" def test_transform_value_with_cast_matching_type(): assert transform_value({}, [int], int, "1") == 1 def test_transform_value_with_cast_matching_base_class(): class MyInt(int): pass assert transform_value({}, [int], MyInt, "1") == 1 @init_var_type_support def test_extract_init_var(): assert extract_init_var(InitVar[int]) == int def test_is_type_generic_with_matching_value(): assert is_type_generic(Type[int]) def test_is_type_generic_with_not_matching_value(): assert not is_type_generic(int)